Uh oh!
There was an error while loading. Please reload this page.
fix(services): the DbJobAdapter class JSDoc stops promising a sys_job_run row that recordRuns: false never writes - #9646
Merged
os-project-manager merged 1 commit intoAug 18, 2026
Conversation
…b_run` row `recordRuns: false` never writes (#9631) The class-level "persisted side effects" list said `every execution writes a sys_job_run row`. `wrap()` gates that insert on `recordRuns`: current = { id: this.recordRuns ? await this.startRun(...) : undefined, ... }; and `settle()` only updates a row when one exists, so with `recordRuns: false` no execution writes one. `tsup` emits this comment into the package's built `index.d.ts`, which is the class-level editor tooltip an npm consumer reads — the same "published documentation asserting behaviour the runtime does not have" class as #9611, in the same file and the same emitted declaration. The corrected bullet defers to `DbJobAdapterOptions.recordRuns` with `{@link}` instead of restating the rule. Two true-but-divergent descriptions of one flag is the next version of this defect, and the field's own JSDoc is where the meaning belongs; the class list only says where it shows up. It also names the one row the flag does not govern — `replay()`'s synthetic `trigger: 'replay'` row, written either way, measured rather than assumed. Without that clause the corrected sentence would be false today for exactly the reason #9633 records. The fourth bullet was already true and gains the matching negative: `bumpJob` is called from `settle` OUTSIDE the `if (run.id)` guard, so the `sys_job` counters are updated whether or not a run row exists. Left implicit, a reader carries the `recordRuns` caveat down onto it. No behaviour change. Five cases pin the flag, reusing this file's existing engine double rather than minting a new one. Nothing in the package referenced `recordRuns` in any direction before, so the wording corrected here and the field wording corrected on #9611 were both accurate but unenforced. The discriminator asserts the execution REALLY RAN — handler fired, `sys_job.run_count` bumped — and that no row was written anyway; without that half, "0 rows" would also pass for a job that never fired. The replay case pins today's behaviour because that is what the JSDoc now states, not as an endorsement: #9633 holds the open disposition, and if it lands the case and the bullet it mirrors change together. Co-authored-by: Claude <noreply@anthropic.com>
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 3 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
os-project-manager
marked this pull request as ready for review
August 18, 2026 14:37
Uh oh!
There was an error while loading. Please reload this page.
os-project-manager
deleted the
claude/issue-9631-dbjobadapter-class-jsdoc
branch
August 18, 2026 14:59
os-project-manager pushed a commit
that referenced
this pull request
Aug 18, 2026
… landed Merged rather than rebased: rebasing a pushed branch needs a force-push, which the dev contract forbids outright. Same outcome, no forced history. Both blockers are now on main (#9635 at c07d6e8, #9646 at 73010f1), so the three points this card was holding are resolved here: 1. Field JSDoc — the clause naming replay as an exception is gone. Not a mechanical delete: it left "Two things are unaffected either way" counting wrong, so the sentence is rewritten to say `false` writes no rows at all, replay included, with the sys_job counters as the one exception. 2. Class JSDoc — the "one row it does not govern" sentence is gone. The bullet keeps its single {@link DbJobAdapterOptions.recordRuns} pointer rather than paraphrasing the flag a second time, per the constraint #9631 was built on. 3. #9646's fifth test case, which pinned "replay writes anyway", is deleted. It carried a comment saying it would change together with the class JSDoc if this card ruled the carve-out shut. It did. A note stands where it was. Test-file conflict was add/add — both describe blocks appended at the end. Both are kept; they cover different paths (#9631's the wrap() path, this card's the replay() path). Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y26DJEHSBhhAQ6wwfsHNza
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#9631
The
DbJobAdapterclass-level JSDoc listed among its persisted side effects:wrap()gates that insert onrecordRuns, so withrecordRuns: falseno executionwrites one.
tsupemits this comment intopackages/services/service-job/dist/index.d.ts,which makes it the class-level editor tooltip an npm consumer of
@objectstack/service-jobreads — the same "published documentation asserting behaviour the runtime does not have"
class as #9611, in the same file and the same emitted declaration. No behaviour changes.
Premise verified before implementing, and one premise that had moved
Re-measured against
origin/main(40162f1e2) rather than taken from the card:wrap()line 305:current = { id: this.recordRuns ? await this.startRun(name, defaultTrigger, attempt) : undefined, settled: false };— the gate is real, andsettle()callsfinishRunonly underif (run.id). The bullet is false whenever the flag isfalse.this.recordRuns = args.options?.recordRuns ?? true— boolean, defaulttrue.bumpJobis called fromsettleoutside theif (run.id)guard, so thesys_jobcounters are updated either way.replay()callsthis.startRun(name, 'replay')with no gate, andfinishRunon all three arms — the exceptionDbJobAdapter.replay()writes its syntheticsys_job_runrow regardless ofrecordRuns— an operator who switched run history off still accumulates replay rows #9633 records, confirmed here rather than assumed.The premise that had moved: the dispatch expected PR #9635 (for #9611) to have landed. It has not — it is still an open draft, so
mainat branch time carries the old field-level comment. I branched frommainas instructed, did not branch from its branch, and read the corrected field wording from its head commit9b07a5e34to match it. The two diffs touchdb-job-adapter.tsabout thirty lines apart (its hunk is theDbJobAdapterOptionsfield comment at lines 31-35; mine is the class JSDoc at 62-77) and share no other file — #9635 touchesmemory-cache-adapter.ts,memory-cache-adapter.test.tsand its own changeset, while my test edits land indb-job-adapter.test.ts. Either merge order is clean.The correction, and why it defers instead of restating
The card's sharpest constraint was not to invent a second, differently-worded
description of one flag — two true-but-divergent descriptions is the next version of this
defect. So the corrected bullet points at the field rather than paraphrasing it:
Three deliberate choices:
.d.tsJSDoc comments inpackages/services/*describe behaviour their code does not have —MemoryCacheAdapter's "LRU-style eviction" is insertion-order, andDbJobAdapterOptions.recordRunsis documented as a numeric cap defaulting to none but is a boolean defaulting to true #9611's field wording ("inserted at the start of every attempt").onAttemptStartfires per attempt, so a retried execution writes more than one row; "every execution writes a row" was loose in that direction too.recordRunsis false" — would have been wrong today for exactly the reasonDbJobAdapter.replay()writes its syntheticsys_job_runrow regardless ofrecordRuns— an operator who switched run history off still accumulates replay rows #9633 records. The honest sentence has to name the exception, so it does.recordRunscaveat down onto it from the bullet above. Named here with its evidence (bumpJoboutside theif (run.id)guard); this is the only line I touched that was not itself false.No issue number went into the published tooltip —
#9633belongs in the test comment, not in what npm renders.The pin: five cases, and why each discriminates
Nothing in this package referenced
recordRunsin any direction before this PR, so boththe field wording #9611 corrected and the class wording corrected here were accurate but
unenforced. The cases reuse this file's existing engine double rather than minting a new
one, so
check:engine-double-contractgains nothing to pin (319 pinned, unchanged).recordRunsdefaults totrue— no options at all, one row writtenrecordRuns: falsewrites NO row though the execution really ran — the discriminatorrecordRuns: falsedoes not gate thesys_jobcounters — pins bullet fourrecordRuns: truematches the default, with the row'strigger/statusshapereplay()writes its synthetic row even when the flag isfalse— pins the exceptionThe second case asserts the handler fired and
sys_job.run_countbumped alongsidethe "no rows" assertion. Without that half, "0 rows" would pass just as well for a job that
never ran at all, which is how a test like this goes quietly blind.
Reverse verification — direction predicted before running, and observed. Ablation
applied to the committed state (the
recordRunsgate removed fromwrap(), sostartRunis unconditional). Predicted: red in exactly cases 2, 3 and 5; 1 and 4 green; all 75
pre-existing tests green. Observed:
The second half is the load-bearing part: all 75 pre-existing tests stayed green under the
ablation. The flag could stop being honoured entirely and this package's suite would not
have noticed — which is precisely how a comment describing it drifted from the code and sat
green through three cards. These are source-level vitest cases, not a dogfood run, so the
ablation needs no rebuild to take effect. Restored with
git checkout claude/issue-9631-dbjobadapter-class-jsdoc -- ...; no marker remains(
grep -c ABLATION-9631= 0) and the tree is clean at the head below.Acceptance: checked in the emitted artifact, not the source
Rebuilt the package and read the published declarations. In both
dist/index.d.tsandits
dist/index.d.ctstwin, the unqualified bullet counts 0, and the qualifiedreplacement counts 1:
Changeset: owed, same reasoning #9611 used
.changeset/service-job-class-jsdoc-recordruns.md,patchon@objectstack/service-job.AGENTS.md exempts pure bug fixes, but this fix's entire deliverable is text inside a
published package's
.d.ts— with no version bump the corrected tooltip never reachesnpm and the card's acceptance is unmet in the only channel it is about. Not breaking, so
no ADR-0087 marker is required.
Verification — union run at
dc61fbaee, the final commitDerived with
node scripts/pm/dispatch-gates.mjsagainst the actual changed paths fromgit merge-base(40162f1e2), per #9320 — notorigin/main..HEAD. Working tree clean.pnpm --filter @objectstack/service-job testpnpm --filter @objectstack/service-job typechecktsc --noEmitcleanpnpm check:nul-bytespnpm check:changeset-gate-self-testspnpm check:objectui-changesetpnpm check:test-source-aliaspnpm check:type-source-resolutionnode scripts/check-adr-0087-registration.mjsnode scripts/check-changeset-no-major.mjsnode scripts/check-empty-changeset.mjsnode scripts/docs-audit/check-affected-docs.mjspnpm check:query-options-erasurepnpm check:engine-double-contractpnpm check:where-matcherpnpm check:type-check-coveragepnpm check:type-check-debt --re-measureThe five convention-triggered families are in that list because this PR adds test code;
the ratchet's
--re-measureneeds the built workspace closure, soturbo run build --filter=./packages/* --filter=./packages/*/*ran first (70/70 successful)— a refusal would have meant NOT MEASURED, not a pass. Every heavy step ran under
flock /tmp/os-heavy-verify.lock.Out of scope
DbJobAdapter.replay()writes its syntheticsys_job_runrow regardless ofrecordRuns— an operator who switched run history off still accumulates replay rows #9633 —replay()writing its synthetic row regardless ofrecordRuns— is a behavioural question needing a disposition and is not addressed here; this PR only stops the JSDoc from contradicting it. If that card lands disposition (1), the fifth test case and the class-JSDoc clause it mirrors change together, which is the reason the case carries a comment saying so.packages/services/service-job/README.md:157describes the same flag as "falsekeeps the in-memory history only", which the replay row falsifies in exactly the same way. Left untouched — a README edit pulls in the published-README gate family this diff does not otherwise touch, and the line's correctness is decided entirely byDbJobAdapter.replay()writes its syntheticsys_job_runrow regardless ofrecordRuns— an operator who switched run history off still accumulates replay rows #9633 (disposition 1 makes it true with no edit). Recorded as a comment on that card rather than scattered into a new one.Generated by Claude Code