Uh oh!
There was an error while loading. Please reload this page.
fix(example-showcase): the nightly health sweep takes its engine off the job handler's argument - #14459
Conversation
…ext argument
The nightly `showcase_health_sweep` handler held its engine in a module-scope
`let host` that `onEnable` filled via an exported `bindShowcaseJobRuntime`.
That binding does not exist on the artifact path: `objectstack build` emits
`functions` into a sibling runtime module exporting only `{ functions, meta }`,
the artifact JSON carries no `onEnable`, and `mergeRuntimeModule` merges only
`functions` -- so on an artifact-served boot the handle stayed `undefined` and
the sweep recomputed nothing while reporting a clean run.
Take `jobId` / `ql` / `logger` off the handler's `JobHandlerContext` argument
(#14094) and delete the binding seam entirely: the module-scope `host`, the
`JobHostContext` / `JobHostEngine` local interfaces, `bindShowcaseJobRuntime`,
its re-export and its `onEnable` call. `effect: 'writes'` on the `functions`
entry is unchanged -- that declaration is about who counts the writes, not
about where the handle comes from.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68Swap the handler test's fake HOST for a fake CONTEXT: `jobRun()` builds the `JobHandlerContext` `AppPlugin` passes, with `ql` declared `satisfies` the contract's own member types so a drift in `IDataEngine.find`/`.update` reds here rather than being absorbed by a hand-written approximation. Adds the case this card is about: reach the handler through its `functions` entry -- the only thing `objectstack build` emits and `mergeRuntimeModule` merges -- with no `onEnable` called anywhere in the file, and assert it still reads and recomputes. Plus two guards that the seam cannot return: the jobs module exports no `bindShowcaseJobRuntime`, and no authored source (comments stripped) carries the binding seam or its "no engine handle bound yet" branch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
…t comment `check:cross-package-test-inputs` is a SOURCE SCAN: a path literal anywhere in a test file is read as one of that test's real inputs, comment or not. Naming `mergeRuntimeModule`'s file by repo path made the gate demand a turbo `inputs` declaration for a file this test never reads. Describe the module instead, and say so in the comment so the next author does not put the path back. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
os-musk
commented
Sep 2, 2026
Landing provenance — engine execution seat (session ACCEPT on the card: comment 5506367294 (#14257). Flip pre-checks on head Action: Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#14257
What was wrong
The showcase's nightly
showcase_health_sweephandler held its engine in a module-scopelet host, filled by an exportedbindShowcaseJobRuntime(ctx)thatobjectstack.config.tscalled fromonEnable. Withhostunset the handler loggedno engine handle bound yet — skipping this runand returned.That binding does not exist on the artifact path:
objectstack buildemitsfunctionsinto a sibling runtime module exporting only{ functions, meta }, the artifact JSON carries noonEnable, andmergeRuntimeModulemerges onlyfunctions. So on an artifact-served boot the sweep fired on schedule, recomputed nothing, and was reported as a clean run. This is the repo's only shippeddefineJob, i.e. what an author copies.What changed
src/automation/jobs/sweep-project-health.ts— the handler destructures{ jobId, ql, logger }from itsJobHandlerContextargument (AdefineJobhandler is invoked with{ jobId, data, bundle }and no data reach, so the platform's only scheduled-work metadata shape cannot read or write a record #14094, type imported from@objectstack/runtime). Deleted: the module-scopehost, the localJobHostContext/JobHostEngineinterfaces,bindShowcaseJobRuntime, and theno engine handle bound yetearly return.loggeris now the platformLoggerthe context carries, so the optional-call spelling goes with it.src/automation/jobs/index.ts— drops thebindShowcaseJobRuntimere-export.objectstack.config.ts— drops the import and theonEnablecall.test/inert-wirings.test.ts— a fake CONTEXT instead of a fake host, plus the artifact-boot case this card is about.effect: 'writes'on thefunctionsentry is unchanged, per the card's triage. That declaration is about who counts the writes — a job has no downstream declarative node to count them, so undeclared a run reports having written nothing rather than "cannot say", which is indistinguishable from the broken sweep #4354 exists to detect. It never said anything about where the handle came from.Two comment regions edited beyond the lines the dispatch named
Both are inside the three files already on the surface, but outside the named lines. Declared here because leaving them would have kept the reference app teaching the superseded shape in prose while its code no longer used it.
objectstack.config.ts, thefunctionsmap docblock. It read "it writes over an engine handle captured atonEnable" — a sentence this PR makes false about the very entry it sits above. Evidence for the replacement wording:JobHandlerContext's own docblock in@objectstack/runtime, andcontent/docs/automation/jobs.mdx, which AdefineJobhandler is invoked with{ jobId, data, bundle }and no data reach, so the platform's only scheduled-work metadata shape cannot read or write a record #14094 already updated to teach the argument (that page is correct as it stands and is untouched here).sweep-project-health.ts,healthFor's docblock. It pointed readers attest/job-health-sweep.test.ts. That file does not exist anywhere in the tree — a repo-wide grep finds this one mention and no file. Repointed attest/inert-wirings.test.ts, where those unit tests actually live.Tests
test/inert-wirings.test.tsnow builds the argumentAppPluginreally passes.qlis declaredsatisfiesthe contract's own member types rather than a local interface, so a drift inIDataEngine.find/.updatereds in this file instead of being absorbed by a hand-written approximation; the cast that follows widens only to the engine members this handler never touches.pnpm check:engine-double-contractis green with no new baseline row.Three cases added, one of them the card's:
functionsentry, the one thingobjectstack buildemits andmergeRuntimeModulemerges, withonEnablecalled nowhere in the file, and assert it reads both objects and writes the recomputed health.bindShowcaseJobRuntime(the seam is gone from the export surface, not merely unused).src/orobjectstack.config.tscarries the seam or its skip branch, judged on comment-stripped text so documentation stays free to explain why it went.Ablation (direction predicted before the run: RED)
Reverted the handler to a module-scope handle in the worktree, on top of the committed implementation. Mutation proved on disk by anchored counts before measuring — injected
no engine handle bound yet1, injectedlet host: JobHostContext1, deleted{ jobId, ql, logger }: JobHandlerContext0, blob hashcd757bamoved toa9f19b9.Restored with
git checkout HEAD -- ABSOLUTE_PATH;git diff HEADempty and the blob hash back tocd757ba, byte-identical.No
dist/leg applies to this ablation, and that is a measured property rather than an assumption:sweep-project-health.tshas exactly one import and it isimport type, so the handler carries no runtime dependency to build, and the test resolves it from source. The source-only mutation flipping the suite red is itself the proof that no build artifact stands between them.One prediction missed, and it is worth reading. I expected four failures; three came.
is a no-op when nothing changedstayed GREEN under the ablation, because it assertswritesis empty and the broken handler writes nothing at all. That assertion passes vacuously against exactly the defect this card is about — the same shape as the production symptom, where a sweep that recomputed nothing was recorded as a clean run.Gates
Run on final HEAD
7988daded(quoted from that same run), aftergit merge origin/mainand a fullinstall --frozen-lockfileplusturbo run buildof the packages closure. Exit codes captured after redirect, never through a pipe.pnpm --filter @objectstack/example-showcase typecheck— green. The app'sincludecoverstest/**, measured rather than assumed: before the test was rewritten this same command printed three errors, all of them intest/inert-wirings.test.ts.pnpm --filter @objectstack/example-showcase exec vitest run --maxWorkers=2—Test Files 26 passed (26),Tests 367 passed (367).pnpm lint— repo-wideeslint . --no-inline-config, exit 0. Not narrowed.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsderives from this diff: all green, plus the always-runspnpm check:error-status-conformance.node scripts/check-test-completeness.mjsexits 3 withPREREQUISITE NOT MET — this gate grades a saved 'turbo run test' log, and no log was named, and its own text says "the local reading for this gate is NOT MEASURED. It is not a red, and there is nothing here to fix." CI passes it the teed log.check:cross-package-test-inputsreds an intermediate commit here and the fix is worth knowing: it is a source scan, so a repo path spelled in a test comment is read as one of that test's real inputs. NamingmergeRuntimeModule's file by path made the gate demand a turboinputsdeclaration for a file the test never reads.Changeset
None, and
skip-changesetapplied.@objectstack/example-showcaseis"private": trueand publishes nothing; thechangeset-checkjob inpr-automation.ymlcounts added.changeset/*.mdunless that label is live-read on the PR, which is the exemption for a PR that declares no release of its own. Recent showcase-only fixes landed the same way.🤖 Generated with Claude Code
https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
Generated by Claude Code
Generated by Claude Code