Skip to content

plugin-security: the first sign-up on a freshly seeded database blocks ~45 s while claimSeedOwnership re-owns every seed row through the full write pipeline — app hooks, record-change flows, approvals and notification emails fire for a bookkeeping write #22067

Description

@objectstack-fleet

Filing gate: ① product defect with reach measured. Class (a). reach: public door, POST /api/v1/auth/sign-up/email (the first human sign-up on a freshly seeded database), which every new deployment and every demo reset passes through. Measured on @objectstack/* 17.7.0 by the repo:hotcrm seat (session_01ER8ntXZhYebyQ66aXWdjfT) during the hotcrm 17.7.0 upgrade (hotcrm PR #2008). The maintainer ruled in that session to file it here: 「只立卡」.

Who acts on it: objectstack triage routes it. Likely domain:security (plugin-security) with a note to the engine lane. ⛔ Not a claim; triage sets type and grade.

Measured

Setup: hotcrm at 56d98f7e built to an artifact, then objectstack start --artifact … -d file:… --log-level debug on an EMPTY database. The default seed is 354 rows ([Seeder] Seed loading complete {"inserted":354,…}). The first user signed up through REST.

run seed settled wait before sign-up first POST /auth/sign-up/email
17.7.0, debug log 32 s after the health check went green 120 s more 45.8 s
17.7.0, browser (console Create Account) — ~45 s 44.3 s
17.7.0, REST, same procedure as the 17.6.0 row — 20 s 34.1 s
17.6.0, REST — ~20 s ~31 s
any later sign-up — — 0.02 s (403: self-registration closes after the first user, as designed)

So this is not a 17.7.0 regression, and it is not the seed still writing: on the debug run the seed had settled two minutes before the request.

Where the 45.8 s go. Request sent at 09:21:45.0Z, answered at 09:22:30.8Z. The log during that window:

  • 09:21:45.5Z — [security] first user promoted to platform admin, then the default-organization bind.
  • 09:21:45.9Z → 09:22:30.6Z, 44.7 s, ending in:
    [security] handed 350 seeded record(s) to platform admin … (17 of 18 eligible object(s) had unowned rows) followed by platform bootstrap complete {"adminPromoted":true,"ownershipClaimed":350,…}.
  • The response is sent immediately after. The claim runs inside the sign-up request.

What those 44.7 s did, counted from the debug log of the window (13,261 lines):

what count
Update operation starting on business objects (crm_campaign 64, crm_opportunity 38, crm_quote 16, …) ~200
[BodyRunner] hook fired — app hooks bound from metadata, run in the QuickJS sandbox (opportunity_amount_rollup, campaign_metrics_refresh, opportunity_lifecycle, lead_automation, …) 1,254
record-change flow start conditions evaluated (Flow '…' skipped: start condition not met) 335
record-change flows that ran: case_escalation ×4, task_urgent_alert ×4 8 runs
approval requests opened (approval node suspended run, flow:opportunity_approval on two seeded deals) 2
notification emails handed to the transport ([LogTransport] would send email) 8
sys_audit_log / sys_activity inserts 392 / 391

Why it matters

  1. Latency on the one request every deployment makes first. The console sits on Creating account… for 45 s. On a slower database, or a larger seed, it grows with the row count. A proxy or client with a 30 s timeout fails the very first sign-up, even though the account and the claim both complete server-side (measured: the account signs in afterwards).

  2. A bookkeeping write fires business automation. Re-owning seed rows to the first admin is attribution, not a user event. Yet it:

    • escalates cases;
    • raises urgent-task alerts;
    • opens approval requests on demo deals;
    • emails the new admin.

    With a real SMTP transport, the first admin's inbox fills at sign-up. The seed itself is written under SEED_WRITE_EXECUTION_CONTEXT (skipTriggers), on the principle the engine states at engine.ts:5413: "seed loads end-state data, not user events". The claim that follows it does not keep that principle.

Code reading (origin/main 8caa131e52)

  • packages/plugins/plugin-security/src/claim-seed-ownership.ts:132: const SYSTEM_CTX = { isSystem: true };
  • :483–487: reown = ql.update(schema.name, { owner_id: adminUserId }, { where: predicate, multi: true, context: SYSTEM_CTX }). The context has no skipAutomations and no skipTriggers, so the predicate write dispatches every per-row after-hook and the record-change trigger.
  • packages/plugins/plugin-security/src/bootstrap-platform-admin.ts:1168: await claimSeedOwnership(ql, chosen.id, …), inside the bootstrap that the first sign-up awaits.
  • packages/objectql/src/engine.ts:4185–4204: session.skipAutomations suppresses hooks bound FROM METADATA (entry.meta). Code-registered hooks run regardless: "audit, capability gates, sharing projection — have no meta and always run: the opt-out must never bypass security or audit (数据导入:批量 insert 给 Hook 的输入形状与单条不一致(installFlatInput 失效);「运行自动化与触发器」开关是摆设且默认值应为选中 #2922)".
  • engine.ts:5413–5419: skipAutomations implies skipTriggers, so the record-change flow dispatch is suppressed too.

A direction, for triage to rule on (⛔ not a ruling)

  • Run the claim's reown writes with { isSystem: true, skipAutomations: true }.
    • The engine already keeps audit and the sharing projection running under that flag. The claim's own comments (the per-row hook budget, the plugin-sharing recompute) depend on exactly those code-registered hooks, not on app hooks.
    • The flag would remove the app hooks, the flows and the approvals/emails, and most of the 44.7 s.
    • Open question for the ruling: does any app-hook contract need to see an owner change made by the claim? An owner-propagation hook would be one. If so, that is the case that decides between skipAutomations and a narrower flag.
  • Separately: should the claim run inside the first sign-up request at all, or after the response (it already settles on app:seeded for later seeds)?
  • Not measured: the effect of either change. An A/B in the hotcrm session that patched the installed bundle was stopped by the session's safety policy (it would have edited installed dependencies). The measurement belongs on an objectstack branch built from source.

Related, not duplicates

Duplicate check

Semantic issue search on objectstack, three queries:

  • claimSeedOwnership seed ownership claim slow first sign-up fires hooks flows automation: 8 hits, all closed. The three above are the related ones; the rest concern dev-admin seeding, seed budgets and owner seeds.
  • first user sign up takes 30 seconds platform admin bootstrap: 0 hits.
  • ownership claim owner_id update triggers record-change flows approvals notifications emails skipAutomations seed: 6 hits, all closed and on other surfaces.

Positive control: the first query surfaces #14530 and #14719, the claim's own cards.

Dedupe words: first sign-up slow · claimSeedOwnership latency · ownership claim fires hooks · seed claim triggers flows · claim skipAutomations · first admin emails on sign-up


Generated by Claude Code

Activity

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

Metadata

Metadata

Assignees

Labels

area:identityLogin and identity — sign-up, sessions, organization membership, SSObugSomething isn't workingdomain:servicespriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions