Skip to content

feat(plugin-auth,plugin-email): 18.x step 3 (8b-0d′): identity mail goes to a deployment-level sibling of sys_email, expand-first #24531

Closed

Description

@objectstack-fleet

Filing gate ③: a task the maintainer directed. These are steps 1–3 of the 18.x line, under ruling records 6266750393 and 6267391172 on #15194. The second records the maintainer's 「用量很多,不用节约」: steps 1–3 start now, in parallel.

Part of #24343 (three framework writers), from decision #24377 Q2 A (ruling 6257510973) · Step 3: 8b-0d′, identity mail goes to a deployment-level sibling of sys_email · ⛔ Not an 18.0 gate.

The line's rule (binding)

Once merged, this PR leaves main releasable:

Expand first. This step only adds: the new write shape, plus reads that accept both the 18.0 shape and the new one. It never moves or deletes an 18.0 row and never drops a column. Each such removal is written down for the step-5 ceremony.

Merge gate. The PR stays a draft until #24522's harness has merged. It lands after it has merged main and is green on R1–R4.

Scope

Acceptance

  • The new shape is pinned on a fresh install. The dual read is pinned on an 18.0-shaped row.
  • The harness stays green.
  • The report lists every removal the step-5 ceremony owes, table by table.
  • Measure first. If expand-first is impossible or unsafe for any part, stop and report that part with the alternative. Do not build a destructive change.

Activity

  1. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 10 · 2026-10-19T12:43Z
    Session: session_01Y5583bs2RDd53MH6kM1ca3
    Account: os-steve (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-24531-8b0d-identity-mail-sibling. If the dev splits this step into several independently releasable PRs, each PR's branch is claude/issue-24531-8b0d-identity-mail-sibling-<part> and carries Part of #24531.
    Worktree: the cloud session's own checkout (mode:cloud)
    Domain: domain:services
    Seat: epic:#15194 (the epic lane)
    File surface, read on origin/main at or after 5b14b7f3a. The dev measures first; a breach stops the work and is explained in the report.

  2. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24531,
    "status": "done",
    "branch": "claude/issue-24531-8b0d-identity-mail-sibling",
    "pr": "#24587",
    "session": "session_016CvQEdxxYYVa8C8AjKZBtL",
    "premise_still_valid": true,
    "summary": "Identity mail is now written to sys_identity_email, a new deployment-level sibling of sys_email. It has the same fields and indexes (held equal by a test), systemFields tenant false so no organization column, append-only, get/list, and manage_platform_settings to read. The contract gains SendEmailInput.identityMail and SendTemplateInput.identityMail; sending one with an organizationId is refused as VALIDATION_FAILED. plugin-email's whole pipeline reads and writes the log the row lives in: send, sendTemplate, persistence, the queue worker, the drain hook, the per-log boot sweep, reclaim and the header readback. A sibling job carries object sys_identity_email; a job with no object reads sys_email, which covers every 18.0 job. plugin-auth's five identity sends (verify, reset, account invitation, magic link, email-change notice) say identityMail and name no organization in every posture; the organization invitation stays tenant mail. Expand-first: no 18.0 row is moved or deleted, and 18.0 sys_email identity rows and jobs are still delivered, swept and reclaimed (pinned on an 18.0-shaped fixture). The inventory row, the census JSON and the tenant-audit census were regenerated. The card's assignee was os-steve at dispatch and was not written; the PR assignee is os-steve.",
    "tests": "All on head 3bc88f5 unless noted. plugin-email: typecheck plus vitest, 34 files, 571 passed. plugin-auth: typecheck plus vitest, 165 files, 3168 passed and 10 skipped, run before the main merge; main's two commits do not touch plugin-auth; identity-mail-sibling, auth-email-locale and verification-email-failure-propagation re-run on the final head, 77 passed. platform-objects: typecheck plus vitest, 78 files, 1188 passed; src/audit re-run on the final head, 27 passed. spec: platform-object-names.test.ts and email-service.test.ts, 15 passed; typecheck exit 0. cli unit: organization-ownership-inventory.test.ts, 25 passed. dogfood: signup-organization-census.dogfood.test.ts, 3 passed (verification mail in sys_identity_email and none in sys_email; the sibling schema has no organization_id, with sys_email having it as the control). Fresh showcase boot (pnpm dev --fresh -p 38531): a password reset and a sign-up verification each wrote one sys_identity_email row, sent, with no organization_id key; invite-member wrote one sys_email row, related_object sys_invitation, stamped with the organization; the server was killed by its recorded PID and the port is free. Ablations via scripts/ablation-replace.mjs, each restored to the HEAD blob with git diff HEAD empty: A1, route every send to sys_email: 4 red. A2, the worker ignores the job's object: 2 red. A3, a job with no object reads the sibling: 11 red, including the 18.0 rowId-job pin. A4, reclaim always reads sys_email: 2 red. A5, plugin-auth verification send without identityMail: 3 red; the first A5 attempt was refused by the tool (replacement was a substring of the anchor) and was a no-op. A6, the same mutation through plugin-auth dist: ablation-dist-preflight found the marker in 2 built files, the dogfood census went 2 red, and after restore, rebuild and preflight --absent it was 3 green; the first A6 attempt's mutated build failed on a TS no-overlap error, so that reading is void.",
    "gates": "Local, head 3bc88f5: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 126 commands; all 126 ran, exit codes were written to a file before any pipe, and all 126 were exit 0. The --ran reconciliation with 'command :: exit N' records reads 126 derived, 126 run, 0 NOT MEASURED (a derived zero). check:dev-prereqs exit 0. Earlier reds were fixed: check-keyed-text-bounds exit 2 (a fields reference it cannot read, now a literal plus an equality test); check-tenant-audit-census exit 1 (regenerated, two prose numbers updated); check-plugin-teardown-shape --self-test exit 3 (shallow clone; fetched its pinned control commit). STDERR artifact-roster block: 51 roster families, NOT MEASURED locally, CI's. Also outside the derived total: 11 wide-population families, 6 workflow-valued families, 5 path-scheduled CI jobs and 4 type-check lanes, all CI's. CI on 3bc88f5 at report time: 'No other open PR may claim the same issue' success; Lint (all lanes), Type Check (4 lanes), Governed Surface Queue Guard, Check Changeset and the rest in_progress; Packed-tarball smoke skipped.",
    "mcp_calls": "0",
    "api_writes": "2 relay strokes: (1) pr_create, which is POST /repos/objectstack-ai/objectstack/pulls (draft) plus POST /repos//issues/24587/assignees, read back byte-identical with assignee os-steve; (2) this report, POST /repos//issues/24531/comments. git push is not counted. Reads used gh api under the session login.",
    "ceremony_owes": [
    "sys_email: move identity rows to sys_identity_email without organization_id. These are rows about a person: related_object sys_user, or none (magic link). Walled 18.0 rows are organization-less; single 18.0 rows carry the Default Organization, and the ceremony must rule whether they move (the ruling names walled rows anchored on sys_user or on nothing). Move only terminal rows (sent/failed). A queued row's pending rowId job reads sys_email and would never deliver after a move.",
    "sys_email attachment reclaim jobs (email.attachment.reclaim) with no object: after a move they take the row-gone branch and DELETE the content. Either rewrite pending payloads to add object sys_identity_email, or move rows only after their reclaim window.",
    "sys_email email.send.async jobs with no object for queued identity rows: drain them before the move, or rewrite their payloads to add object.",
    "sys_activity: rows whose object_name or source_object is sys_email for a moved row point at a missing record; re-point or delete them (Q3 A's deletion covers the organization-less ones).",
    "sys_email.organization_id NOT NULL (8e): only after the move leaves zero NULL rows.",
    "step 7: the 'no object reads sys_email' default stays correct for tenant jobs; once no 18.0 job can still be pending, nothing identity-specific is left to remove.",
    "outside this repo or card: cloud's wall-test non-vacuity control is rewritten against the sibling (cloud lane, per the ruling); the ADR-0131 D7 Q2 A note's 'has not shipped' line needs its dated update (Tier H, docs/adr off this card)."
    ],
    "open_questions": [
    {
    "question": "Decided: the sibling's name and the contract member's name.",
    "options": [
    "A sys_identity_email plus identityMail (the ruling's 'identity mail' words)",
    "B sys_platform_email plus a scope enum (sys_platform_setting's precedent)"
    ],
    "recommendation": "A, taken: it names what the rows are, matches the ruling's vocabulary, and a boolean is the smallest widening of the contract."
    },
    {
    "question": "Decided: identityMail sent together with organizationId.",
    "options": [
    "A refuse as VALIDATION_FAILED and record the rejection in the sibling",
    "B drop the organization silently",
    "C let organizationId win and write to sys_email"
    ],
    "recommendation": "A, taken: contract-first; either silent choice writes the row somewhere its caller did not mean."
    },
    {
    "question": "Decided: who may read sys_identity_email.",
    "options": [
    "A manage_platform_settings, as sys_job and sys_platform_setting",
    "B open reads like sys_email"
    ],
    "recommendation": "A, taken: with no column there is no wall (D7), and the rows carry reset and sign-in links."
    },
    {
    "question": "Decided: under single, identity mail no longer carries the Default Organization.",
    "options": [
    "A identity mail goes to the sibling in every posture",
    "B only under a wall"
    ],
    "recommendation": "A, taken: the ruling makes identity mail deployment-level outright, and one route per mail class keeps the readers single."
    }
    ],
    "out_of_scope_findings": [
    "class: none filed. Observation: sys_email declares no requiredPermissions while 18.0 identity rows there carry reset links; whether a non-administrator can list them was NOT MEASURED (the showcase registrant could not sign in unverified). reach: exception security, unmeasured. evidence: packages/platform-objects/src/audit/sys-email.object.ts enable.apiMethods get/list with no requiredPermissions. dedupe words: sys_email read permission, reset link exposure, requiredPermissions sys_email",
    "carrier: none (承接者:无). check:keyed-text-bounds reads only a literal fields map, so an object that reuses another's fields must spell them out; noted in the PR's Acceptance notes."
    ]
    }


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report addendum: #24552 harness run on PR #24587

    • Merged main into the branch: head cd6fe0b4f, a clean merge with no regeneration deferral. pnpm install --frozen-lockfile and the dogfood dependency-closure build both exit 0.
    • Harness run: pnpm --filter @objectstack/dogfood exec vitest run test/upgrade-18x-harness.dogfood.test.ts test/signup-organization-census.dogfood.test.ts, through os-verify-lock. Result: 2 files, 8 passed, VERDICT exit 0. Per test:
      • Fixture coverage: passed.
      • 1 (R1, R2 boot): HEAD boots over the 18.0 database, with no ownership refusal.
      • 2: the fixture's people sign in, and its task is there.
      • 3 (R2 data): every reshaped table keeps every 18.0 row and its key columns, including the fixture's 2 stamped sys_email rows.
      • 4 (R3): a v17 trait is still refused at boot.
    • R4 not measured: the harness has no walled-posture case, so this run says nothing about R4.
    • The seat's four F3 constraints, from message 6268559156's step plan, checked against the build:
      1. No reader selects by organization_id IS NULL. Every reader goes by table, id and status. The stamped 18.0 rows stay in sys_email and keep being delivered, swept and reclaimed.
      2. The four identity send sites go to the sibling. The organization invitation stays on sys_email.
      3. The drain hook, the queue consumer, the outbox sweep, reclaim and the header readback all cover both tables.
      4. The sibling is a new table without the column, and sys_email is untouched.
    • The PR stays a draft. Landing is the seat's: after the harness is green on the merged head and CI has run on cd6fe0b4f.

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed and closed · epic PM session_01Y5583bs2RDd53MH6kM1ca3 (os-steve) · 2026-10-19T17:03Z. PR #24587 (18.x step 3, 8b-0d′: identity mail moves to a deployment-level sibling of sys_email) has merged.

    reading value
    PR #24587 merged: true at 2026-10-19T16:49Z; merge commit fbafb8533
    contract review 6271215222, CONTRACT_REVIEW_TIER, schema face, PASS
    R2 harness 8/8 on the merged tree, cd6fe0b4f (addendum 6270136912)

    Delivered (a complete feature, per 6270035914).

    • New identity mail goes to sys_identity_email. That covers reset, account invitation, verification, magic link and change-email. The table has no organization column and sits behind manage_platform_settings. The organization invitation stays on sys_email.
    • Every outbox reader (drain, queue consumer, sweep, attachment reclaim, header readback) runs over both tables. 18.0 identity rows in sys_email are still sent, retried and reclaimed, and none is moved.

    Carried.

    The card closes completed. The dev session is archived.

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions