Repository navigation
Commit c40506a
docs(qa): RUNNER.md reads the connector self-URL from /meta/connector and states the scheduled-work switch (#24156)
Fixes #24119
Clause-②: no
Docs only: `docs/qa/platform-checklist/RUNNER.md`, the two runner facts
the card names, both inside "Environment facts the runner should not
re-derive". Nothing else in the file moved. The "The record title"
section is untouched, because #24127 may change it. No area file, no
product code, no changeset: the diff ships nothing from any package, and
the seat applies `skip-changeset` at review.
Authored by the os-dev dispatched for this card by the `domain:devx`
seat 2, session `session_016PVjjuVPte5XEJvaQx1E4t`, branch
`claude/issue-24119-runner-facts`.
## How both facts were re-measured
Both were measured on `main` at `2889760a5`, the branch point, by
reading the code and by two `os dev` boots of `examples/app-showcase`.
The boots ran after a full `pnpm build`, on the RUNNER's canonical boot
line (`objectstack dev --ui --seed-admin -p PORT -d file:...`):
- **Boot A**: `-p 47311`, environment untouched.
- **Boot B**: `OS_PORT=47312 OS_AUTOMATION_SCHEDULED_WORK_ENABLED=1`,
`-p 47312`.
Both boots served the same
`examples/app-showcase/dist/objectstack.json`. The build compiled it
with no `OS_PORT`, so its two self-pinging connectors hold
`providerConfig.baseUrl: http://127.0.0.1:3000`. Nothing listened on
3000.
## Passage 1: the connector self-URL check
**Old** (the bullet's `Check:` sentence; the rest of the bullet is
unchanged):
```text
**Check:** before scoring any connector clause, `GET /api/v1/automation/connectors` and read the
instance `baseUrl` — if it says `` and your server does not listen there, the
fixture is misconfigured, not blocked.
```
**New:**
```text
**Check:** before scoring any connector clause, read the instance's own metadata —
`GET /api/v1/meta/connector/showcase_status_api`, field `item.providerConfig.baseUrl`,
which is the value `resolveShowcaseSelfUrl()` produced
(`examples/app-showcase/src/system/connectors/index.ts`). ⛔ Not
`GET /api/v1/automation/connectors`: that route lists registry descriptors — `name`,
`label`, `type`, `description`, `icon`, `origin`, `state`, `degradedReason`, `actions`
(`getConnectorDescriptors()`, `packages/services/service-automation/src/engine.ts`) —
and carries no `baseUrl` at all. On `os dev` the value follows the serving process's
environment, not the compiled artifact. Measured on `main` at `2889760a`: `-p 47311`
alone answered `http://127.0.0.1:3000`, and `showcase_declarative_connector_ping`
(fired by a `showcase_task` create) failed `fetch failed`; `OS_PORT=47312` with
`-p 47312` answered `http://127.0.0.1:47312` and the same flow completed, while
`dist/objectstack.json` still held `http://127.0.0.1:3000`. Only an artifact-only boot
(`os start --artifact`) freezes the value at build time (the `self-url.ts` header). If
it names a port your server does not listen on, the fixture is misconfigured, not
blocked.
```
**Sources and measurements:**
- `GET /api/v1/automation/connectors` is served by the `parts[0] ===
'connectors'` branch of `packages/runtime/src/domains/automation.ts`.
That branch answers `getConnectorDescriptors()` from
`packages/services/service-automation/src/engine.ts`, whose projection
is `name, label, type, description, icon, origin, state, degradedReason,
actions`. It has no `baseUrl` and no `providerConfig`. Measured on both
boots: every row's keys were exactly `actions, description, icon, label,
name, origin, state, type`, and the string `baseUrl` does not occur in
the body.
- `GET /api/v1/meta/connector/showcase_status_api` answers the declared
item. Its `item.providerConfig.baseUrl` is the value
`resolveShowcaseSelfUrl()` produced
(`examples/app-showcase/src/system/connectors/index.ts`). Boot A
answered `http://127.0.0.1:3000`. Boot B answered
`http://127.0.0.1:47312`, while the artifact on disk still said 3000.
- The materialized connector dials that value. On boot A, creating a
`showcase_task` fired `showcase_declarative_connector_ping`, which
failed with `Node 'ping' failed:
connector_action(showcase_status_api.request) failed: fetch failed`. On
boot B the same create produced a `completed` run whose `ping.body`
carried `status: 'ok'`.
- The value is read at boot, not at build, for this reason. The showcase
config is a host config: its `plugins:` array holds instances
(`isHostConfig`, `packages/cli/src/utils/plugin-detection.ts`). So the
`serve` child loads `objectstack.config.ts` itself and wraps it in `new
AppPlugin(config)` (`packages/cli/src/commands/serve.ts`), and
`resolveShowcaseSelfUrl()` reads the serving process's environment. `os
dev` hands `-p` to that child as `--port` and never exports `OS_PORT`
(its `localEnv`, `packages/cli/src/commands/dev.ts`). So the bullet's
existing claim that `-p` alone is not enough still holds, and that part
was left as written.
**Disproved half of the card's reading.** The card recorded that "`GET
/api/v1/meta/connector` shows the build-time `http://127.0.0.1:3000`".
On an `os dev` boot that value is not build-time. It follows the serving
process's environment: boot B answered 47312 over an artifact holding
3000. 3000 is what any boot without `OS_PORT` or `SHOWCASE_SELF_URL`
answers. The new text says this, so the next runner does not reason from
the artifact.
## Passage 2: scheduled work
**Premise correction.** RUNNER.md had no scheduled-work passage, so
there was nothing stale to rewrite:
- `git show c91af8e:docs/qa/platform-checklist/RUNNER.md | grep -ci
schedul` prints 0 (exit 1).
- The file is byte-identical between `c91af8ed6`, the card's measurement
commit, and `2889760a5`: `git diff --stat` prints nothing.
- `git merge-base --is-ancestor c91af8e 2889760` exits 0. A positive
answer proves itself, even on this shallow checkout.
The fact was missing, not stale. The card's Done-when ("RUNNER.md states
both facts as the runtime answers them") is met by adding it as one new
environment-fact bullet, right after the connector bullet. Old text:
none.
**New:**
```text
- **Package-authored scheduled work is OFF on a stock boot — no `schedule` or
`time_relative` flow binds and no packaged `defineJob` is scheduled unless you export
`OS_AUTOMATION_SCHEDULED_WORK_ENABLED=1`.** The switch is an environment read,
`resolveScheduledWorkEnabled()` / `resolveScheduledWorkPolicy()` in
`packages/types/src/env.ts`: unset, empty, or anything other than `true` / `1` / `on` /
`yes` (case-insensitive) is off, in every tenancy posture, and `os dev` sets nothing for
you. Every binder reads that one answer — the automation engine's trigger bind and its
`/_status` row (`packages/services/service-automation/src/engine.ts`), the schedule
trigger (`packages/triggers/trigger-schedule/src/schedule-trigger.ts`), and AppPlugin's
declarative-job loop, which logs `declarative jobs NOT scheduled — …` at info level
(`packages/runtime/src/app-artifact-handlers.ts`). It does not gate platform-internal
jobs (approvals escalation, the lifecycle Reaper, the messaging dispatch loop).
**Check:** the boot banner. Measured on `main` at `2889760a` with the canonical boot
below: a default boot printed `32 flow(s) 19 bound` and `1 flow declares a 'schedule'
trigger but is NOT bound — disabled by deployment policy …: showcase_scheduled_digest`
(the same line for `time_relative`: `showcase_task_due_reminder`), and
`GET /api/v1/automation/_status` answered `bound: false` with that reason for both; the
same boot with the switch exported printed `21 bound`, `_status` answered
`bound: true` for both, and `showcase_scheduled_digest` completed its first run
(`trigger.type: 'schedule'`) within a minute. **Fix:** export the switch before the boot,
beside `OS_PORT`. ⛔ Do not score a clock-driven clause on a default boot, and do not
file the unbound flow: it is the deployment policy working, not a trigger defect.
```
**Sources and measurements:**
- The reader is `resolveScheduledWorkEnabled()` in
`packages/types/src/env.ts`. It reads
`OS_AUTOMATION_SCHEDULED_WORK_ENABLED` and is truthy only for `1`,
`true`, `on` or `yes`. Its docblock reads "Default OFF, in every posture
and every kernel". `resolveScheduledWorkPolicy()` and
`scheduledWorkDisabledReason()` in the same file are what the engine's
bind, the `/_status` row and the schedule trigger read. AppPlugin's job
loop logs `declarative jobs NOT scheduled — …` at info level
(`packages/runtime/src/app-artifact-handlers.ts`, the
`!resolveScheduledWorkEnabled()` branch).
- `os dev` does not set it. Neither `packages/cli/src/commands/dev.ts`
nor `serve.ts` names the variable, and `dev`'s `localEnv` does not carry
it.
- Boot A's banner printed `Flows: 32 flow(s) 19 bound …` and the two
`NOT bound — disabled by deployment policy` lines, for
`showcase_task_due_reminder` and `showcase_scheduled_digest`. `_status`
answered `bound: false` for both, with the policy reason.
- On boot B the banner printed `21 bound`, and `_status` answered
`bound: true` for both. `showcase_scheduled_digest` completed its first
run at 2026-10-17T05:51:55Z with `trigger.type: 'schedule'`, less than a
minute after boot.
## Gates
All gates below were run on the final head `4094dd433`, which is
`d76548150` (the edit) plus a merge of `main` at `6dede7a51`. The merged
commit touches none of the paths cited above. Each exit code was
captured before any pipe.
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` (no paths) derived 13 commands from a 1-path
change set. Every one exited 0: `check-ci-filter-parity`,
`check-closing-keyword-parity` and its `--self-test`,
`check-comment-mask-corpus`, `@objectstack/lint
check:doc-formula-expressions`, `check:cross-package-test-inputs`,
`check:doc-authoring`, `check:driver-memory-census`,
`check:gitlink-declared`, `check:nul-bytes`, `check:platform-checklist`,
`check:refd-timer-probe` and `check:watch-hint-literal`.
- `check:platform-checklist` printed: `check-platform-checklist: OK — 16
areas, 327 items (322 active, 1 planned)`.
- `--ran` reconciliation: `13 derived famil(ies) accounted for — 13 run,
0 NOT-MEASURED (a DERIVED zero — all 13 recorded an exit code and none
of them is 3)`.
- `node scripts/pm/check-governed-merges.mjs --branch
claude/issue-24119-runner-facts`: `0 of 1 path(s) hit the register` and
NOT governed, 42 changed lines.
- CI check-runs had not concluded when this PR was opened.
## Acceptance notes
- The area files already name the switch per item (`automation.json`,
`integration-system.json`, `platform-core.json`, `approvals.json`). The
new bullet states it once, for every item and every runner.
- Not measured: `serve.ts` says that on an `os dev` boot of a host
config, the dev artifact door's copy of the stack replaces the config
wrap's copy on the first artifact reload. After a watch rebuild,
`/meta/connector` would therefore follow the recompiled artifact.
Whether that recompile sees an exported `OS_PORT` was not measured. Both
boots above were cold boots with no rebuild. No carrier.
---
_Generated by [Claude
Code](https://claude.ai/code/session_016PVjjuVPte5XEJvaQx1E4t)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent 1fee13d commit c40506a
1 file changed
Lines changed: 39 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
229 | 229 | | |
230 | 230 | | |
231 | 231 | | |
232 | | - | |
233 | | - | |
234 | | - | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
235 | 248 | | |
236 | 249 | | |
237 | 250 | | |
238 | 251 | | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
239 | 275 | | |
240 | 276 | | |
241 | 277 | | |
| |||
0 commit comments