Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .changeset/20281-job-pull-organization.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
---
'@objectstack/spec': minor
'@objectstack/runtime': minor
Expand All @@ -12,7 +12,7 @@
- **`JobSchema.organization`**. The organization a job runs as. It applies to the body's `ctx.api`, to the handler's new `executionContext`, and to the pull's reads and writes. The value shape is the scheduled flow's: a non-empty `sys_organization.id`. A near-miss spelling (`organizationId`, `orgId`, `tenantId`, …) is refused at parse and pointed at the key.
- **`defineStack`, and so `os validate`**, refuses a job whose `pull` names a mapping the stack does not declare, or a mapping with no `connectorSource`. The refusal is the existing `STACK_CROSS_REFERENCE_INVALID` envelope.
- **`IAutomationService.pullConnectorSource`** (`@objectstack/spec/contracts`, with `ConnectorSourcePullRequest`, `ConnectorSourcePullResult` and `ConnectorSourcePullSummary`). The connector sync executor is now on the `automation` service. `@objectstack/service-automation`'s engine serves it from the executor `AutomationServicePlugin` attaches at init (`AutomationEngine.setConnectorPullSource`). A bare engine refuses with `SERVICE_UNAVAILABLE` (503).
- **The job binder** (`@objectstack/runtime`, `scheduleAppArtifactJobs`) schedules a `pull` job on every door: the boot, and `os package install` on install and rehydrate. Each run calls `pullConnectorSource` through the service registry. A refused pull fails the run, and `retryPolicy` applies. A pull whose rows the import runner refused records the run `degraded`, with the counts. A pull naming a mapping the artifact does not carry is not scheduled, and neither is one whose mapping has no `connectorSource`, nor one on a kernel whose `automation` service cannot pull. Each case is logged at `warn` with the reason. `collectJobsWithoutBody` no longer names a `pull` job, so `os package install` does not refuse one. The result gains `pulls` and `missingOrganization`.
- **The job binder** (`@objectstack/runtime`, `scheduleAppArtifactJobs`) schedules a `pull` job on every door: the boot, and `os package install` on install and rehydrate. Each run calls `pullConnectorSource` through the service registry. A refused pull fails the run, and `retryPolicy` applies. A pull whose rows the import runner refused records the run `degraded`, with the counts. A pull naming a mapping the artifact does not carry is not scheduled, and neither is one whose mapping has no `connectorSource`, nor one on a kernel whose `automation` service cannot pull. Each case is logged at `warn` with the reason. `collectJobsWithoutBody` does not name a `pull` job that binds, so `os package install` installs one. The result gains `pulls` and `missingOrganization`.
- **The organization, judged at bind** by the posture rule scheduled flows use (`resolveScheduledWorkPolicy`). Every run carries `{ isSystem: true, tenantId: <organization> }`, or `{ isSystem: true }` for a job that declares none. Under `single` the key is not required. Under `group` it is optional; an undeclared job is scheduled and named once at `warn`, because a tenant-scoped row it writes is refused. Under `isolated`, with package-authored scheduled work switched on, it is **required**. **Action on such a deployment:** declare `organization` on each packaged job, or the job is not scheduled; the error log names the job. Until now such a job was scheduled, and every tenant-scoped write it made was refused at the write. An unrecognized `OS_TENANCY_POSTURE` withholds every job (`scheduled-work-policy-unreadable`) instead of guessing whether a declaration is required.
- **Texts this makes true.** The `mapping.connectorSource` description, the `connector.syncConfig` tombstone prescription and the `connector-sync-keys-retired` upgrade entry said "nothing schedules a pull yet". They now name the `job` `pull` that drives it.

Expand Down
19 changes: 19 additions & 0 deletions .changeset/21672-install-local-pull-refusal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
'@objectstack/runtime': minor
'@objectstack/cloud-connection': minor
---

fix(runtime,cloud-connection)!: install-local refuses an enabled job whose `pull` does not bind, as it refuses a job `body` that does not bind (#21672)

Clause-②: yes (narrowing)

<!-- adr-0087: not-required (no-migration-prescription) no authorable key, spelling, export of a published release or stored shape moves: `JobSchema` and `MappingSchema` are unchanged, so `objectstack migrate meta` has nothing to rewrite. What changes is which packages one install door accepts. The other categories are closed on facts: the packages publish (not `unpublished`); no ADR-0087 id covers a refused install (not `registered` / `already-registered`); and the change is runtime behaviour, not a declaration (not `runtime-interface-only` / `type-surface-only`). -->

**BREAKING**: `os package install` (the install-local door, `POST /api/v1/marketplace/install-local`) now refuses a package whose enabled job declares a `pull` that does not bind. It used to install such a package with a 200, and the job was never scheduled; only a server warn said so.

- **What does not bind.** The `pull` names a mapping the package does not declare, or a mapping with no `connectorSource`, or the job declares `body` or `handler` beside its `pull`. The door judges this with the scheduler's own judgement, so the door and the scheduler cannot disagree. `defineStack` and `os validate` already refuse the same `pull`, so only a hand-edited package reaches the door with one.
- **The refusal.** The install answers `422` with `VALIDATION_ERROR`, the answer the door already gives an enabled job whose `body` does not bind. One answer names everything the door cannot run, and gives each such job the reason its `pull` does not bind, prefixed with the key it names (`pull.mapping: …`). Nothing is installed: nothing is registered, persisted or scheduled. `os package install` exits non-zero and prints the code beside the status.
- **Unchanged.** A pull job naming a declared mapping with a `connectorSource` installs and is scheduled as before. A disabled pull job does not block its install. A package installed by an earlier version still rehydrates after a restart, and its pull job that does not bind is not scheduled, with a warn naming the job and the reason, as before.
- **Runtime.** `collectJobsWithoutBody` now names an enabled job whose `pull` does not bind, and `JobWithoutBody` gains an optional `pullRefusal`: the reason the scheduler gives when it does not schedule the job. Such a job carries no `bodyRefusal`.

The route for a refused package: declare the mapping the job's `pull` names in the package, with a `connectorSource` naming the `rest` or `openapi` connector it reads from, or correct the `pull` as the refusal says. `os validate` refuses the same `pull`. This ships as `minor`, under the launch-window convention for narrowings of an accept set.
8 changes: 6 additions & 2 deletions content/docs/automation/jobs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,8 @@ export const CloseStaleTasksJob = defineJob({
package whose enabled job has no `body`, or a `body` that does not bind (an
expression body, or one carrying `body.timeoutMs`), with `422 VALIDATION_ERROR`
and the remedy: give the job a valid `body`, or boot it with `os start --artifact`.
A [`pull`](#pulling-a-mapping) job is data too, and is not refused.
A [`pull`](#pulling-a-mapping) job is data too: it installs when its `pull`
binds, and is refused with the same `422` when it does not.
Uninstalling a package stops its scheduled jobs at once, and a reinstall whose
new version drops a job stops that job.
</Callout>
Expand Down Expand Up @@ -249,7 +250,10 @@ export const OrdersPullJob = defineJob({
- **Checked when you build.** `defineStack` — and so `os validate` — refuses a
`pull` that names a mapping the stack does not declare, or one with no
`connectorSource`. The binder checks the same reference against the artifact
before it schedules the job, on every door.
before it schedules the job, on every door, and `os package install` refuses a
package whose enabled job's `pull` fails that check, with `422 VALIDATION_ERROR`
naming the job and the reason. A package an earlier version installed still
loads after a restart; such a job of it is not scheduled, and a warning names it.
- **Each run is one pull.** It calls the connector's read action once, reads one
response, and writes the records through the import runner with the mapping's
`mode` and `upsertKey` — see
Expand Down
Loading
Loading