SchemaRegistry.resolveObjectKey rescans the ENTIRE object registry on every name lookup, so kernel boot is quadratic in how much metadata an environment has stored.
// packages/objectql/src/registry.ts:2366
private resolveObjectKey(name: string): string | undefined {
const matches: string[] = [];
for (const fqn of this.objectContributors.keys()) { // ← every registered object
const { shortName } = parseFQN(fqn); // ← per entry, per lookup
if (shortName === name) matches.push(fqn);
}
…
}
It is called from at least seven sites in the same file (1842, 2346, 2453, 2476, 2703, 3133, 3365) — getObject(name) among them — so a boot that registers N objects and looks up O(N) names does O(N²) string work with parseFQN as the inner loop.
Measured
Instrumenting a real per-environment kernel build (ArtifactKernelFactory.create(), full hosted capability slate, real TursoDriver in remote transport over a local libsql file) and sweeping the number of env-wide sys_metadata rows the kernel hydrates at boot (ObjectQLPlugin({ hydrateMetadataFromDb: true }), which the cloud tenant runtime always sets):
sys_metadata rows bootstrap step marginal ms/row database round trips
0 63 ms — 242
500 232 ms 0.338 243
1,000 422 ms 0.380 243
2,000 1,036 ms 0.614 243
4,000 2,627 ms 0.798 243
8,000 8,533 ms 1.496 315
Round trips are FLAT — this is not I/O. Log-log fit of (bootstrap − baseline) against row count: exponent 1.413, R² = 0.994.
A --cpu-prof of the 8,000-row boot names the loop directly. Self time attributed to parseFQN + resolveObjectKey + getPackagedObjectOwner:
sys_metadata rows registry-scan self time
1,000 80 ms
2,000 192 ms (2.4x)
4,000 631 ms (3.3x)
8,000 2,325 ms (3.7x)
Each doubling multiplies the cost by ~3.7 — exponent ≈ 1.89, i.e. quadratic. parseFQN is the single largest non-database entry in the profile.
Why it matters
This is the mechanism behind objectstack-ai/cloud#1555: a hosted environment whose bootstrap step takes 134 s against a 20 s request waiter, so every request answers kernel_warming and the environment can never be opened. The build never fails — it is just slower than anything will wait for. Boot cost that is quadratic in stored metadata means an environment gets permanently unservable purely by being used, with no error anywhere.
Direction
objectContributors is keyed by FQN; the short-name→FQN direction wants its own index maintained alongside it (a Map<shortName, fqn[]> updated in register/unregister), turning every one of these lookups into O(1). The ambiguity warning the current loop emits is preserved by keeping the array of matches per short name.
Two properties worth pinning with the fix: a lookup must not depend on registration order, and unregisterObject must keep both maps in step — the extraction note at registry.ts:2350 says the read and the name-addressed removal must not disagree about which contributor a bare name addresses, and a second index is exactly where that could drift.
Reproducing
objectstack-ai/cloud: node scripts/dev-local/bootstrap-curve.mjs --hosted --meta-rows 0,1000,4000,8000
Refs objectstack-ai/cloud#1555
SchemaRegistry.resolveObjectKeyrescans the ENTIRE object registry on every name lookup, so kernel boot is quadratic in how much metadata an environment has stored.It is called from at least seven sites in the same file (1842, 2346, 2453, 2476, 2703, 3133, 3365) —
getObject(name)among them — so a boot that registers N objects and looks up O(N) names does O(N²) string work withparseFQNas the inner loop.Measured
Instrumenting a real per-environment kernel build (
ArtifactKernelFactory.create(), full hosted capability slate, realTursoDriverin remote transport over a local libsql file) and sweeping the number of env-widesys_metadatarows the kernel hydrates at boot (ObjectQLPlugin({ hydrateMetadataFromDb: true }), which the cloud tenant runtime always sets):Round trips are FLAT — this is not I/O. Log-log fit of (bootstrap − baseline) against row count: exponent 1.413, R² = 0.994.
A
--cpu-profof the 8,000-row boot names the loop directly. Self time attributed toparseFQN+resolveObjectKey+getPackagedObjectOwner:Each doubling multiplies the cost by ~3.7 — exponent ≈ 1.89, i.e. quadratic.
parseFQNis the single largest non-database entry in the profile.Why it matters
This is the mechanism behind objectstack-ai/cloud#1555: a hosted environment whose
bootstrapstep takes 134 s against a 20 s request waiter, so every request answerskernel_warmingand the environment can never be opened. The build never fails — it is just slower than anything will wait for. Boot cost that is quadratic in stored metadata means an environment gets permanently unservable purely by being used, with no error anywhere.Direction
objectContributorsis keyed by FQN; the short-name→FQN direction wants its own index maintained alongside it (aMap<shortName, fqn[]>updated in register/unregister), turning every one of these lookups into O(1). The ambiguity warning the current loop emits is preserved by keeping the array of matches per short name.Two properties worth pinning with the fix: a lookup must not depend on registration order, and
unregisterObjectmust keep both maps in step — the extraction note at registry.ts:2350 says the read and the name-addressed removal must not disagree about which contributor a bare name addresses, and a second index is exactly where that could drift.Reproducing
objectstack-ai/cloud:node scripts/dev-local/bootstrap-curve.mjs --hosted --meta-rows 0,1000,4000,8000Refs objectstack-ai/cloud#1555