fix(runtime,cloud-connection)!: install-local refuses an enabled job whose pull does not bind (#21672) - #21683
Conversation
… job whose pull does not bind Written ahead of the fix: run against unmodified main, the refusal pins reproduce the reach (the door answers 200) and the controls hold. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz
…whose pull does not bind collectJobsWithoutBody judges a pull job by the binder's own judgeJobPull and names an unbindable one with its pullRefusal; describeUnrunnable gains the pull clause. The door answers 422 VALIDATION_ERROR. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz
The jobs page states the refusal and the rehydrate behaviour. The unreleased stage-3 changeset said install-local never refuses a pull job, which this change makes false; it now says a pull job that binds installs. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz
📓 Docs Drift CheckThis PR changes 2 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 28 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 76e15c6c1fbef88a242c0f6bfa18c80c6b1ecb8e && git checkout 76e15c6c1fbef88a242c0f6bfa18c80c6b1ecb8e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 7d0781482dbb6502aa33c29bec96ca03636f7df9 413869dfe1121301bbe00bfa4860cd9cf32cf258 && git checkout -B drift-repro 7d0781482dbb6502aa33c29bec96ca03636f7df9 && git merge --no-ff 413869dfe1121301bbe00bfa4860cd9cf32cf258
node scripts/docs-audit/affected-docs.mjs --json 7d0781482dbb6502aa33c29bec96ca03636f7df9
|
|
Release-note correction, confirmed (
Generated by Claude Code |
Contract reviewServed-tier: ① Derived judgmentsInputs read. Card #21672: the filing, triage grade Accept-set changes.
Public-surface changes.
One judge. The collector calls Landed names. Rehydrate unchanged, as claimed. The binder's function body is not in the diff: the hunks in Docs. Gate verdicts on the head. 35 runs, all ② Semver level
③ Boundary flagsDev deviations (report
Governance. The file list is Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #21672
Clause-②: yes (narrowing)
Built to triage
5975994778(direction) and5976336256(unlocked once PR #21668 landed as909229e976), dispatched under claim5976423110. This is PR #21615's shape: one pull clause in the existing unrunnable judgement, read from the samejudgeJobPullthe binder schedules by. There is no second judge.What changes
The install-local door (
packages/cloud-connection,POST /api/v1/marketplace/install-local,os package install) now refuses a package whose enabled job declares apullthat does not bind. It used to install it with a 200, and the binder then warned and never scheduled the job.judgeJobPull's answer: thepullnames a mapping the package does not declare, or a mapping with noconnectorSource, or the job declaresbodyorhandlerbeside thepull.422 VALIDATION_ERROR, the code and status the door already gives a jobbodythat does not bind. No new error code.describeUnrunnablegains a pull clause beside the body clauses. It names each such job with the refusaljudgeJobPullgives (pull.mapping: …), and the remedy: declare the mapping with aconnectorSource, or correct thepull.os validaterefuses the samepull.body: that would send the author to write abodybeside thepull, the shape the declaration refuses.os package installexits 1 and printsInstall failed (422 VALIDATION_ERROR).connectorSourceinstalls and is scheduled, as before.The ONE binder (
packages/runtime/src/app-artifact-handlers.ts).collectJobsWithoutBody(the landed name, kept:packages/spec/liveness/job.jsonanchors on it) now judges a job that declarespullby callingjudgeJobPull(job, bundle), the function the binder calls before it schedules a pull job. A pull that binds is not named. A pull that does not bind is named with its refusal.JobWithoutBodygains one optional field,pullRefusal: the refusaljudgeJobPullgives. A job carrying it carries nobodyRefusal, since apullis judged before anybodybeside it, as in the binder.Docs:
content/docs/automation/jobs.mdxnow says the install door refuses an enabled job whosepulldoes not bind. That replaces "Apulljob is data too, and is not refused". It also says what happens on rehydrate.Unchanged:
packages/spec,service-automationandobjectqlare untouched. So areJobSchema,MappingSchema, the binder's scheduling, the boot door and the error-code ledger.A pending release note this change makes false, corrected here (confirmation requested)
.changeset/20281-job-pull-organization.md(PR #21668, not yet released) says:This PR makes that false. It now reads:
Nothing else in that note changed.
check-empty-changesetnames this case its DELIBERATE CORRECTION class, soCheck Changesetstays red on this PR by design. That context is not required. Its own text asks for the correction to be confirmed in writing on the PR, and ⛔ neverskip-changeset. Restoring the note from the base would ship the false sentence in the same release as this PR's own changeset.Measured before (A1), at the public door, on
origin/maineed2dee481Measured with the new integration file below against unmodified runtime and cloud-connection
dist/, as part of the CLI's dependency closure built ateed2dee481:orders_pul) installed with exit 0:Package installed into the running kernel.WARN:[MarketplaceInstallLocal] job pull does not bind — the job is NOT scheduled: pull.mapping: this artifact declares no mapping 'orders_pul' — …, with{"appId":"com.example.pullmissing","job":"pull_missing_orders"}on the line.connectorSourceinstalled the same way, and the warn saidpull.mapping: mapping 'orders_pull' declares no connectorSource, so there is nothing to pull — ….sys_jobrow).One judge (A2)
judgeJobPull(job, bundle). That is the same function, with the same arguments, thatscheduleAppArtifactJobscalls before it schedules a pull job. The door only formats thepullRefusalit is handed, and does not re-judge or paraphrase the question.pullRefusalis the exact tail of the warn the binder logs when it withholds that job.collectJobsWithoutBodyandJobWithoutBodyare not renamed.packages/spec/liveness/job.jsonanchorsjob/enabledon the collector and thepullrow onjudgeJobPull. Both rows are unchanged, andscripts/liveness/evidence.test.tspasses (42).Rehydrate (A4): it already held, so it is pinned, not coded
On
origin/mainthe binder's existing skip already withheld a non-binding pull job of a persisted entry and warned with the job's name in the line's meta. The rehydrate pins in the integration file were green before the fix and stay green after it. No rehydrate code was added.Pins
packages/cli/test/package-install-local-jobs-pull.integration.test.ts: exit 1,Install failed (422 VALIDATION_ERROR), names the job,pull.mapping: …andos validate; not in the ledger, nosys_jobrow.cloud-connectionmarketplace-install-local-jobs.test.ts: 422, nothing registered, persisted or scheduled (not even a valid body job beside it), and the no-bodyclause is not used.connectorSourceis refused the same waysys_jobrow, and asys_job_runrow per run. Each run reaches the automation service's pull door, which recordsfailedbecause the package declares noconnectors[]entry. That is the run's verdict, not the install's. cloud-connection unit: 200, scheduled, and a run callspullConnectorSourcewith the mapping.sys_jobrow); cloud-connection unitsys_joborsys_job_runrow, and aWARNline names it withpull.mapping: …. cloud-connection rehydrate unit: the same, with the warn'sjobmeta.app-artifact-handlers.job-pull.test.ts(the two pins above)The runtime pin that asserted the old behaviour,
collectJobsWithoutBody never names a pull job, is replaced by the two collector pins above.Reverse verification (A5)
One leg went through
scripts/ablation-replace.mjsin WRAP mode, with its restore trap held by the tool. It was rebuilt, checked withscripts/ablation-dist-preflight.mjs, then measured. The leg was taken on committed413869dfe1.The door's acceptance condition has no pull-specific term: it refuses on
unrunnable.jobs.length. So the door's pull clause, as a judgement, is the collector's pull leg, and that is what was ablated. AblatingdescribeUnrunnable's sentence alone would leave the 422 standing and change only prose.if (judged.binds) continue;+ newline +pullRefusal = judged.refusal;→ the same with|| String('ABLATED_21672_PULL') !== ''added to the condition, so every pull job is skipped, as onmaind207bdb16564→f4e6ffa8924bdist/index.jsandindex.cjsPackage installed, and the ledger pin.d207bdb16564== HEAD,git diff HEADempty. Rebuilt, then--absent: marker absent from all 6 built files, tree clean.The direction was red, as expected. The tool refused a first attempt before running anything: that replacement still contained the anchor, so the anchor count could not drop. It restored the file and nothing was measured.
Verification (at
413869dfe1)All runs are at
413869dfe1, the final commit, with build and test runs underos-verify-lock.origin/mainhas since moved one commit, to7d0781482d. That commit touches only.claude/skills/pm-dispatch/references/execution-duties.md, so this branch was not merged again.@objectstack/runtime:typecheckgreen, includingcheck:test-typecheck. Full suite (vitest run --project local): 320 files, 4555 passed, 19 skipped.@objectstack/cloud-connection:typecheckgreen, includingtsconfig.test.json. Full suite: 36 files, 443 passed.@objectstack/cli:typecheckgreen. Its test-layer program compiles the new integration file, counted with--listFilesOnly(1 hit).--project unit: 257 files, 3771 passed.runtimeandcloud-connectiondist/(the pull clause present in both door bundles, the ablation marker absent):package-install-local-{jobs-pull,jobs,jobs-shared-name,hooks,handlers,boot-steps,uninstall-cleanups}, 7 files, 76 passed.pnpm --filter @objectstack/spec exec vitest run --project repo scripts/liveness/evidence.test.ts: 42 passed. Every touched symbol was grepped acrosspackages/spec/liveness/**and*.ledger.*: only the two unchanged anchors hit.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(no paths; 9 paths vs merge baseeed2dee48): 93 commands. 92 exit 0, and 1 exits 1 by design:check-empty-changeset --base origin/main, the pending release note corrected above.--ranreports 93 derived, 93 run, 0 NOT-MEASURED, 0 UNRUN.check:skill-examplesandcheck:dual-build-cjs-loadsfirst exited 3 (PREREQUISITE NOT MET), because packages outside this diff's closure were unbuilt. Both exited 0 on rerun once those packages were built. The record carries the reruns.pnpm lint(eslint . --no-inline-config): exit 0, no findings.Acceptance notes
content/docs/references/system/job.mdxis generated fromJobSchema.body's describe inpackages/spec. It saysos package install"refuses an enabled job with nobody(apulljob excepted: it is data too)". That stays literally true, because the exception is from the no-bodyrefusal. It is not edited, sincepackages/specis out of this card's surface. The next PR that touchespackages/spec/src/system/job.zod.tscould add that an unbindablepullis refused too. Not filed.@objectstack/runtimebehind an older@objectstack/cloud-connectionwould describe an unbindable pull job with the no-bodyclause. That is the wrong remedy, though still a 422. The two packages are in onefixedrelease group in.changeset/config.json, and the door already tells an operator to upgrade them together. Not filed.Generated by Claude Code