Repository navigation
Commit 83e2fee
Fixes #21672
Clause-②: yes (narrowing)
Built to triage `5975994778` (direction) and `5976336256` (unlocked once
PR #21668 landed as `909229e976`), dispatched under claim `5976423110`.
This is PR #21615's shape: one pull clause in the existing unrunnable
judgement, read from the same `judgeJobPull` the 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 a `pull` that does not bind. It
used to install it with a 200, and the binder then warned and never
scheduled the job.
- "Does not bind" is exactly `judgeJobPull`'s answer: the `pull` names a
mapping the package does not declare, or a mapping with no
`connectorSource`, or the job declares `body` or `handler` beside the
`pull`.
- **One answer:** `422 VALIDATION_ERROR`, the code and status the door
already gives a job `body` that does not bind. No new error code.
- `describeUnrunnable` gains a pull clause beside the body clauses. It
names each such job with the refusal `judgeJobPull` gives
(`pull.mapping: …`), and the remedy: declare the mapping with a
`connectorSource`, or correct the `pull`. `os validate` refuses the same
`pull`.
- A pull job is never described as a job with no `body`: that would send
the author to write a `body` beside the `pull`, the shape the
declaration refuses.
- Nothing is registered, persisted or scheduled. `os package install`
exits 1 and prints `Install failed (422 VALIDATION_ERROR)`.
- A **disabled** pull job does not block its install, as a disabled body
job does not.
- A pull job naming a declared mapping with a `connectorSource` installs
and is scheduled, as before.
**The ONE binder (`packages/runtime/src/app-artifact-handlers.ts`).**
- `collectJobsWithoutBody` (the landed name, kept:
`packages/spec/liveness/job.json` anchors on it) now judges a job that
declares `pull` by calling `judgeJobPull(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.
- `JobWithoutBody` gains one optional field, **`pullRefusal`**: the
refusal `judgeJobPull` gives. A job carrying it carries no
`bodyRefusal`, since a `pull` is judged before any `body` beside it, as
in the binder.
- The binder itself is unchanged. Its TSDoc now says install-local
refuses the shape up front, so on that door the binder's pull warn fires
only on a rehydrate.
**Docs:** `content/docs/automation/jobs.mdx` now says the install door
refuses an enabled job whose `pull` does not bind. That replaces "A
`pull` job is data too, and is not refused". It also says what happens
on rehydrate.
**Unchanged:** `packages/spec`, `service-automation` and `objectql` are
untouched. So are `JobSchema`, `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:
> `collectJobsWithoutBody` no longer names a `pull` job, so `os package
install` does not refuse one.
This PR makes that false. It now reads:
> `collectJobsWithoutBody` does not name a `pull` job that binds, so `os
package install` installs one.
Nothing else in that note changed. `check-empty-changeset` names this
case its DELIBERATE CORRECTION class, so **`Check Changeset` stays 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 ⛔ never
`skip-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/main`
`eed2dee481`
Measured with the new integration file below against unmodified runtime
and cloud-connection `dist/`, as part of the CLI's dependency closure
built at `eed2dee481`:
- A pull job naming an undeclared mapping (`orders_pul`) installed with
exit 0: `Package installed into the running kernel`.
- The server said, at `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.
- A pull job whose mapping has no `connectorSource` installed the same
way, and the warn said `pull.mapping: mapping 'orders_pull' declares no
connectorSource, so there is nothing to pull — …`.
- Both packages were in the install-local ledger, and neither job was
ever scheduled (no `sys_job` row).
- The file's three refusal pins went red and its five controls went
green.
## One judge (A2)
- The collector calls `judgeJobPull(job, bundle)`. That is the same
function, with the same arguments, that `scheduleAppArtifactJobs` calls
before it schedules a pull job. The door only formats the `pullRefusal`
it is handed, and does not re-judge or paraphrase the question.
- Pinned in the runtime unit: on one bundle, every pull job the
collector names is one the binder does not schedule. Every enabled pull
job it does not name, the binder schedules. Each named job's
`pullRefusal` is the exact tail of the warn the binder logs when it
withholds that job.
- `collectJobsWithoutBody` and `JobWithoutBody` are not renamed.
`packages/spec/liveness/job.json` anchors `job/enabled` on the collector
and the `pull` row on `judgeJobPull`. Both rows are unchanged, and
`scripts/liveness/evidence.test.ts` passes (42).
## Rehydrate (A4): it already held, so it is pinned, not coded
On `origin/main` the 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
| Pin | Where |
|:---|:---|
| An undeclared mapping is refused at the public door |
`packages/cli/test/package-install-local-jobs-pull.integration.test.ts`:
exit 1, `Install failed (422 VALIDATION_ERROR)`, names the job,
`pull.mapping: …` and `os validate`; not in the ledger, no `sys_job`
row. `cloud-connection` `marketplace-install-local-jobs.test.ts`: 422,
nothing registered, persisted or scheduled (not even a valid body job
beside it), and the no-`body` clause is not used. |
| A mapping with no `connectorSource` is refused the same way | CLI
integration (exit 1, names the job and the reason); cloud-connection
unit |
| One answer names every kind | cloud-connection unit: a handler-only
job and an unbindable pull job in one 422 |
| Control: a declared mapping installs and is scheduled | CLI
integration: exit 0, its `sys_job` row, and a `sys_job_run` row per run.
Each run reaches the automation service's pull door, which records
`failed` because the package declares no `connectors[]` entry. That is
the run's verdict, not the install's. cloud-connection unit: 200,
scheduled, and a run calls `pullConnectorSource` with the mapping. |
| A disabled unbindable pull job installs | CLI integration (exit 0, no
`sys_job` row); cloud-connection unit |
| Rehydrate of an entry an earlier build persisted withholds the job |
CLI integration, second boot over a ledger entry: the bindable pull job
of the entry is scheduled and runs, the unbindable one has no `sys_job`
or `sys_job_run` row, and a `WARN` line names it with `pull.mapping: …`.
cloud-connection rehydrate unit: the same, with the warn's `job` meta. |
| One judge | runtime `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.mjs` in WRAP mode, with
its restore trap held by the tool. It was rebuilt, checked with
`scripts/ablation-dist-preflight.mjs`, then measured. The leg was taken
on committed `413869dfe1`.
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. Ablating
`describeUnrunnable`'s sentence alone would leave the 422 standing and
change only prose.
| Leg | Anchor → mutation | Blob | dist preflight | Went red | Stayed
green | Restore |
|:---|:---|:---|:---|:---|:---|:---|
| The collector's pull leg (the door's pull judgement) | `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 on `main` | `d207bdb16564` →
`f4e6ffa8924b` | marker present in `dist/index.js` and `index.cjs` |
runtime collector unit 2/21. cloud-connection jobs unit 3/17: both
refusals and the one-answer pin. CLI integration 3/8: both refusals, CLI
printed `Package installed`, and the ledger pin. | the declared-mapping
control, the disabled pull job, and the rehydrate pins (all three
layers) | the tool: blob `d207bdb16564` == HEAD, `git diff HEAD` empty.
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
under `os-verify-lock`. `origin/main` has since moved one commit, to
`7d0781482d`. That commit touches only
`.claude/skills/pm-dispatch/references/execution-duties.md`, so this
branch was not merged again.
- `@objectstack/runtime`: `typecheck` green, including
`check:test-typecheck`. Full suite (`vitest run --project local`): 320
files, 4555 passed, 19 skipped.
- `@objectstack/cloud-connection`: `typecheck` green, including
`tsconfig.test.json`. Full suite: 36 files, 443 passed.
- `@objectstack/cli`: `typecheck` green. Its test-layer program compiles
the new integration file, counted with `--listFilesOnly` (1 hit).
`--project unit`: 257 files, 3771 passed.
- The install-local integration pins, on built `runtime` and
`cloud-connection` `dist/` (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 across `packages/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 base
`eed2dee48`): 93 commands. 92 exit 0, and 1 exits 1 by design:
`check-empty-changeset --base origin/main`, the pending release note
corrected above. `--ran` reports 93 derived, 93 run, 0 NOT-MEASURED, 0
UNRUN.
- `check:skill-examples` and `check:dual-build-cjs-loads` first 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.
- Full `pnpm lint` (`eslint . --no-inline-config`): exit 0, no findings.
## Acceptance notes
- `content/docs/references/system/job.mdx` is generated from
`JobSchema.body`'s describe in `packages/spec`. It says `os package
install` "refuses an enabled job with no `body` (a `pull` job excepted:
it is data too)". That stays literally true, because the exception is
from the no-`body` refusal. It is not edited, since `packages/spec` is
out of this card's surface. The next PR that touches
`packages/spec/src/system/job.zod.ts` could add that an unbindable
`pull` is refused too. Not filed.
- Version skew: a newer `@objectstack/runtime` behind an older
`@objectstack/cloud-connection` would describe an unbindable pull job
with the no-`body` clause. That is the wrong remedy, though still a 422.
The two packages are in one `fixed` release group in
`.changeset/config.json`, and the door already tells an operator to
upgrade them together. Not filed.
---
_Generated by [Claude
Code](https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 38bef8c commit 83e2fee
9 files changed
Lines changed: 777 additions & 54 deletions
File tree
- .changeset
- content/docs/automation
- packages
- cli/test
- cloud-connection/src
- runtime/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
15 | | - | |
| 15 | + | |
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
176 | 176 | | |
177 | 177 | | |
178 | 178 | | |
179 | | - | |
| 179 | + | |
| 180 | + | |
180 | 181 | | |
181 | 182 | | |
182 | 183 | | |
| |||
249 | 250 | | |
250 | 251 | | |
251 | 252 | | |
252 | | - | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
253 | 257 | | |
254 | 258 | | |
255 | 259 | | |
| |||
0 commit comments