Skip to content

Setup-field pattern budget measures worker boot + IPC, so a valid value can be rejected and a healthy pattern mislabelled #607

Description

@Weegy

Follow-up from #599 / #606. Found while validating the first real plugin manifest against the shipped feature.

Summary

matchWithBudget in middleware/src/plugins/setupFieldPattern.ts measures a window that includes worker startup and IPC round-trip, not just regex execution. Two consequences, both timing-dependent — which means they surface as intermittent "my correct value was rejected" reports, the hardest kind to diagnose.

  1. A valid value can be rejected fail-closed because the machine was busy, not because the value was wrong.
  2. A healthy pattern gets recorded in the problems registry and is then reported to the operator as "this field declares a format check that could not be applied" — permanently, for the life of the process.

Both land in exactly the failure class the customer test report was about: the UI stating something that is not true.

Measurements (compiled dist, i.e. the production path, node 22.22.3)

Trivial pattern ^[^@\s]+@[^@\s]+\.[A-Za-z]{2,63}$ against assistant@te-printline.de, five sequential calls in a fresh process:

call 1:   838.0 ms  → accepted
call 2:    10.8 ms  → accepted
call 3:    11.7 ms  → accepted
call 4:     4.0 ms  → accepted
call 5:    35.7 ms  → accepted

Two things stand out:

  • Call 1 costs 838 ms — worker boot dominates. It happened to be accepted in that run, but in another run on the same machine the equivalent first call overran and was rejected, emitting:
    [setup] pattern match exceeded 50ms for gw_subject_default; treating the value as a violation.
    
    So whether the first credential save after a restart succeeds is a coin flip on machine load.
  • Call 5 took 35.7 ms for a trivial match — 71% of the entire budget, with no regex work to speak of. The 4–36 ms spread on identical input is IPC and scheduling noise, so a loaded host crossing 50 ms is not exotic.

The tsx path is worse still: driving the module under node --import tsx makes every call overrun — even /^a$/ against 'a', at 84–212 ms — because the worker re-runs tsx's loader. Once a worker is terminated the next call pays boot again, so it becomes self-sustaining. node --test does not hit this, which is why the test suite is green.

Registry poisoning

An overrun writes into getPatternProblems():

registry: [{"context":"gw_subject_default",
            "pattern":"^[^@\\s]+@[^@\\s]+\\.[A-Za-z]{2,63}$",
            "reason":"match exceeded the 50ms execution budget"}]

Every subsequent call with that same pattern succeeded in 4–12 ms — the pattern is completely fine. But the entry persists, and per #599's design that registry is what surfaces "this field declares a format check that could not be applied" to the operator. A single unlucky first call permanently mislabels a healthy pattern.

Why the design is still right

To be clear: the worker budget is the correct mechanism and should stay. A regex cannot be interrupted on the main thread, and #599 measured ^(a|a)+$ blocking the event loop for 1739 ms at a 26-character subject. Nothing here argues for removing it — only for measuring the right window.

Suggested fixes

  1. Start the clock when the worker acknowledges the job, not when it is dispatched. The budget should bound regex execution, which is the only thing that can run away.
  2. Warm the worker at startup, or keep it pooled and pre-booted, so no operator-facing request ever pays the ~838 ms boot.
  3. Do not write a budget overrun into the pattern-problems registry. An overrun is evidence about this execution, not about the pattern. Log it, count it, and only mark the pattern problematic after it overruns repeatedly (say N consecutive times) — mirroring the circuit-breaker pattern already used for plugin activation.
  4. Consider raising the budget, or making it adaptive to observed IPC latency. 50 ms is tight when the measurement floor is already ~10 ms of pure overhead.
  5. Fix the tsx case or document it, so anyone driving this module in dev outside node --test is not silently blocked.

Repro

// from middleware/, after npm run build
import { checkSetupFieldPattern } from './dist/plugins/setupFieldPattern.js';
const f = { key: 'email', type: 'string', label: 'e',
            pattern: String.raw`^[^@\s]+@[^@\s]+\.[A-Za-z]{2,63}$` };
for (let i = 1; i <= 5; i++) {
  const t = process.hrtime.bigint();
  const r = await checkSetupFieldPattern(f, 'assistant@te-printline.de');
  console.log(i, Number(process.hrtime.bigint() - t) / 1e6, r === null ? 'accepted' : 'REJECTED');
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions