You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(core): the pre-injected job fallback must not fake capability — take job off the pre-injection list so scheduled reports actually run on ObjectKernel (#11205)
* fix(core): stop pre-injecting the job fallback — a fallback must not fake capability
On an ObjectKernel without @objectstack/service-job, preInjectCoreFallbacks()
registered createMemoryJob() for the 'job' slot before Phase 2, so
getService('job') always resolved — and that fallback's schedule() records a
job and never fires it. Every 'prefer the platform job service, else own a
timer' consumer took the job-service branch and then silently never ran:
plugin-reports logged 'dispatcher registered with job service' and dispatched
nothing, ever (measured: 0 reads of sys_report_schedule in 5600 ms with the
success line present).
Per the maintainer ruling of 2026-08-22 (issue 10746, Option A — declare only
what you enforce), 'job' comes off the pre-injection list
(CORE_FALLBACK_FACTORIES). getService('job') now throws when no job plugin is
installed; every consumer's documented no-job-service path takes over, and
validateSystemRequirements() says the absence out loud at boot. createMemoryJob
stays exported for deliberate, explicit registration.
Acceptance pin: dispatcher-runs-on-object-kernel.test.ts boots ObjectKernel +
ObjectQLPlugin + ReportsServicePlugin with no job plugin and asserts
sys_report_schedule is actually polled — red before this fix (0 reads), green
after. Discovery-honesty gates keep the memory-job product in their inventory
via the still-exported factory.
Part of #10746
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RfyXxZ2WPjcjhuXpiQQc3y
* docs(kernel): services-checklist no longer advertises a pre-injected job fallback
The page asserted in four places that the kernel pre-injects an in-memory
job fallback — all four made false by taking job off the pre-injection
list. Corrected: the key-architecture principle's slot list (job now named
beside auth as deliberately without a kernel fallback), Service Overview
row 15 (Plugin Required, with the throw-and-warn behavior), the
Infrastructure Services intro (cache/queue keep the fallback claim; job's
core criticality explicitly unchanged — it is what makes the absence
loud), and the job row of the infrastructure table.
Judged still true and left alone: the Framework legend (generic marker
definition, names no job), the i18n fallback notes (i18n stays
pre-injected), the plugin-layer ASCII diagram (lists job as
plugin-delivered — no fallback claim), and the scheduled-tasks provider
row (names service-job, claims no fallback).
Part of #10746
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RfyXxZ2WPjcjhuXpiQQc3y
---------
Co-authored-by: Claude <noreply@anthropic.com>
`ObjectKernel` no longer pre-injects the in-memory `job` fallback for the `job` core-service slot — a fallback must not fake capability (#10746, maintainer ruling 2026-08-22). `createMemoryJob()`'s `schedule()` records a job and never fires it (it owns no timer), so pre-injecting it made every "prefer the platform job service, else own a timer" consumer take the job-service branch on a kernel without `@objectstack/service-job` and then silently never run: `plugin-reports` logged `dispatcher registered with job service` and dispatched nothing, ever.
6
+
7
+
Behavior change, FROM → TO: on an `ObjectKernel` without a registered `job` service, `getService('job')` FROM resolving a non-scheduling in-memory registry TO throwing `Service 'job' not found`. Consumers' documented no-job-service paths take over (`plugin-reports` falls through to its own `setInterval` and scheduled reports actually dispatch; schedule triggers and declarative jobs warn loudly instead of scheduling into the void), and the kernel says the absence out loud at boot: `Core service missing, functionality may be degraded: job`.
8
+
9
+
One-line fix if you relied on the old behavior: install `@objectstack/service-job` for real scheduling, or — if you deliberately want the manual-trigger in-memory registry — register it explicitly: `kernel.registerService('job', createMemoryJob())` (the factory is still exported from `@objectstack/core`).
Copy file name to clipboardExpand all lines: content/docs/kernel/services-checklist.mdx
+4-4Lines changed: 4 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -16,7 +16,7 @@ package catalog.
16
16
17
17
The ObjectStack protocol defines **15 kernel services** registered via the `CoreServiceName` enum (v17 removed the never-implemented `graphql` entry and retired the never-filled `workflow` slot, #4451). Each service maps to a set of protocol methods governed by its per-domain contract (`DataProtocol`, `MetadataProtocol`, ...) — the transitional `ObjectStackProtocol` composition alias was dissolved in v17 (ADR-0076 D9); capability availability comes from the runtime discovery `services` registry.
18
18
19
-
**Key architecture principle**: the kernel guarantees only **data** and **metadata**, and even those are filled by packages (`@objectstack/objectql`, `@objectstack/metadata`) rather than baked in — the kernel's own contribution is an in-memory fallback for the `core` slots that have one (`metadata`, `cache`, `queue`, `job`, `i18n` — **not**`auth`). Everything else — including **auth** and **automation** — is delivered by plugins. `@objectstack/objectql` is an example kernel implementation to get the basic API running; production kernels will be rebuilt as separate plugins.
19
+
**Key architecture principle**: the kernel guarantees only **data** and **metadata**, and even those are filled by packages (`@objectstack/objectql`, `@objectstack/metadata`) rather than baked in — the kernel's own contribution is an in-memory fallback for the `core` slots that have one (`metadata`, `cache`, `queue`, `i18n` — **not**`auth`, and not `job`: an in-memory registry cannot fire a `schedule()`d job on its own, so pre-injecting one advertised a scheduler that never ran, and a fallback must not fake capability — #10746. The `job` slot stays empty, loudly, until `@objectstack/service-job` or an explicitly registered scheduler fills it). Everything else — including **auth** and **automation** — is delivered by plugins. `@objectstack/objectql` is an example kernel implementation to get the basic API running; production kernels will be rebuilt as separate plugins.
20
20
21
21
<Callouttype="info">
22
22
**Legend**
@@ -79,7 +79,7 @@ The ObjectStack protocol defines **15 kernel services** registered via the `Core
| 15 |**job**|`core`| — |❌ Plugin Required (no pre-injected fallback since #10746 — with no job plugin, `getService('job')` throws and the boot warns) |`@objectstack/service-job`|
83
83
84
84
<Callouttype="info">
85
85
The Provider column mirrors `CORE_SERVICE_PROVIDER` in
@@ -480,15 +480,15 @@ AppPlugin will:
480
480
481
481
## 11–15. Infrastructure Services
482
482
483
-
`cache`, `queue`, and `job` are `core` services: like `i18n`, the kernel auto-injects an in-memory fallback when no plugin registers them (see `CORE_FALLBACK_FACTORIES` in `packages/core/src/fallbacks/`). The `optional` services (`storage`, `search`) stay disabled until a plugin provides them.
483
+
`cache`, `queue`, and `job` are `core` services. For `cache` and `queue` — like `i18n` — the kernel auto-injects an in-memory fallback when no plugin registers them (see `CORE_FALLBACK_FACTORIES` in `packages/core/src/fallbacks/`). `job` is deliberately **not** on that list (#10746): an in-memory registry cannot fire a `schedule()`d job on its own, and a fallback must not fake capability — so with no job plugin installed, `getService('job')` throws, consumers take their documented no-scheduler paths (e.g. the reports dispatcher's own `setInterval`), and the boot warns `Core service missing, functionality may be degraded: job`. The `core` criticality itself is unchanged — it is exactly what makes the absence loud. Fill the slot with `@objectstack/service-job`, or register `createMemoryJob()` explicitly if a manual-`trigger()` registry is genuinely wanted. The `optional` services (`storage`, `search`) stay disabled until a plugin provides them.
484
484
485
485
| Service | Description |
486
486
|:--------|:------------|
487
487
|**storage** (deprecated v17 alias: `file-storage`, #9683) | Unified upload/download/delete via `@objectstack/service-storage`, which mounts `/api/v1/storage` itself. Adapters: local FS and S3 (the S3 adapter's `endpoint` + path-style options cover S3-compatible services such as MinIO and R2). |
488
488
|**search**|**Nothing ships.**`ISearchService` and the engine enum (`elasticsearch`, `meilisearch`, …) exist in `@objectstack/spec`, but no package implements the contract or registers the `search` slot, so `CORE_SERVICE_PROVIDER.search` is `null`. |
489
489
|**cache**| General-purpose cache. In-memory fallback; memory or Redis adapter via `@objectstack/service-cache`. |
490
490
|**queue**| Message queue. In-memory fallback; durable DB-backed adapter (`sys_job_queue`) via `@objectstack/service-queue` (no BullMQ/Redis adapter is shipped). |
491
-
|**job**| Scheduled task execution via `@objectstack/service-job`. In-memory fallback; interval, cron, and DB-backed adapters with concurrency policy. |
491
+
|**job**| Scheduled task execution via `@objectstack/service-job` — interval, cron, and DB-backed adapters with concurrency policy. No pre-injected fallback (#10746): install the plugin, or the slot stays empty and the boot says so. |
0 commit comments