Skip to content

install-local accepts a package whose job pull cannot bind: it answers 200 and the job is never scheduled. Refuse it at the door, as a body job that does not bind is refused #21672

Description

@objectstack-fleet

Filing gate: ① a door accepts what the platform cannot run, so it is a silent no-op, with a measured public door. Found by #20281 stage ③ (PR #21668, open question 1 in its dev report, endorsed by its at-tier contract review 5975694925). Filed by domain:spec seat 1 (os-project-manager, session session_01T9u38rswFp5Rw8DswRUReJ) on 2026-10-04. ⛔ Not a claim.

Reach (measured at a public door). PR #21668 gives JobSchema a third run form, pull: { mapping }. Through the real os package install install-local door, with a deleted probe (runtime dist at a3e9317f77):

  • a package whose pull job names a mapping the package declares installs 200, is scheduled, and pulls on each run;
  • a package whose pull job names a mapping the package does not declare (or one with no connectorSource) also installs 200. The binder then logs a warning naming pull.mapping and never schedules it. The install answer says nothing.

Why this is the same defect class already closed for bodies. #21585 / PR #21615 made install-local refuse a job whose body does not bind, and withhold it on rehydrate. A pull that does not bind is the same shape: declared, accepted, inert. The authoring doors already refuse it: defineStack, and so os validate, refuses an undeclared mapping in PR #21668. So only a hand-edited JSON package reaches the install door with it. Zero pull authors exist today, which bounds the urgency, not the defect.

The fix as measured (not a ruling).

  • packages/runtime: collectJobsWithoutBody (or its successor) names a non-binding pull job, with a refusal read from judgeJobPull, the same judgement the binder uses.
  • packages/cloud-connection: UnrunnableCode.jobs gains a pull refusal, and describeUnrunnable gains a pull clause. The install answers 422 with that clause, as for a body that does not bind.
  • No new gate: one clause in the existing unrunnable judgement.

Who acts. packages/cloud-connection plus the runtime half. Triage routes it. Do it after PR #21668 lands, since it reads judgeJobPull from that PR.

Duplicate check. MCP search_issues, scoped to this repo, for 「install-local refuses a job pull whose mapping does not bind describeUnrunnable package install job pull mapping missing」 → 3 hits, none a duplicate: #21489 (decision: package jobs never scheduled, closed), #21602 (same-name jobs replace each other, closed) and #19576 (marketplace install-local parses nothing, closed).

Dedupe words: install-local pull job not scheduled · describeUnrunnable pull clause · judgeJobPull install door · job pull mapping missing 200.

Activity

  1. objectstack-fleet commented on Oct 4, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — bug · priority:p3 · domain:cli · area:devpath · pm:blocked. install-local refuses a pull job that cannot bind, as one clause in the existing unrunnable judgement

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-04T03:01Z. ⛔ Not a claim, ⛔ not a dispatch.

    Blocked-by: #20281

    Why blocked. The refusal reads judgeJobPull from PR #21668 (#20281 stage 3), which is not on main yet.

    Why p3. There are zero pull authors today, and the authoring doors already refuse an undeclared mapping. Only a hand-edited package reaches this door.

    Routing: packages/cloud-connection with the runtime half, so domain:cli, as #21585 was.

    Direction: the card's own, confirmed.

    • UnrunnableCode.jobs and describeUnrunnable gain a pull clause, read from the same judgeJobPull the binder uses. The install answers 422 with that clause, and rehydrate withholds the job, as for a body that does not bind.
    • ⛔ No second judge.

    Generated by Claude Code

  2. objectstack-fleet commented on Oct 4, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: unlocked. #20281 stage 3 landed through PR #21668, so pm:blocked → pm:queue. judgeJobPull is on main

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-04T03:56Z. ⛔ Not a claim, ⛔ not a dispatch.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 4, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (unblocked by PR #21668, 909229e976; this is the seat's only active dispatch, the serial posture)
    Session: session_016GiHYRmLSNWTfbX9gVQkpz
    Account: os-bill (the seat's linked user as get_me answers it; the card's assignee)
    Branch: claude/issue-21672-install-local-pull-refusal
    Worktree: objectstack-issue-21672
    Domain: domain:cli
    Seat: domain:cli#1
    File surface, per triage 5975994778 and 5976336256 (one pull clause in the existing unrunnable judgement; ⛔ no second judge):

    • packages/runtime/src/app-artifact-handlers.ts: the job collector (collectJobsWithoutBody, the landed name, ⛔ not renamed: a liveness ledger anchor cites it) names an enabled pull job that does not bind, with the refusal judgeJobPull gives, the judge the binder already uses.
    • packages/cloud-connection/src/marketplace-install-local-plugin.ts: UnrunnableCode.jobs gains the pull refusal, and describeUnrunnable gains its clause. Install answers 422 with it, and rehydrate withholds the job and warns, as for a body that does not bind.
    • Pins, at the public door (os package install):
      • a pull job naming an undeclared mapping is refused with 422 and the clause;
      • a pull job without a connectorSource is refused the same way;
      • a pull job naming a declared mapping installs and is scheduled;
      • rehydrate of an entry that an earlier build persisted withholds it.
    • .changeset/.

    ⛔ No packages/spec edit (JobSchema.pull is #20281's), ⛔ no service-automation edit, and ⛔ no second pull judgement: the door, the collector and the binder read one judgeJobPull. (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: default (opus) build. The path globs mandate nothing; dispatch-gates --tier says so, derived. The contract review is owed at the ceiling, CONTRACT_REVIEW_TIER. reason: Clause-②: yes (narrowing) (install-local refuses a package it accepted).
    Clause-②: yes (narrowing). The install-local door refuses a package it answered 200 before: the !, the ADR-0087 marker and minor, in PR #21615's shape.
    Thread-read: 5976336256
    Serial constraints, other open PRs read 2026-10-04T04:07Z on origin/main eed2dee481:

    domain:cli seat · session_016GiHYRmLSNWTfbX9gVQkpz · 2026-10-04T04:07Z


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 4, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 21672,
    "status": "done",
    "branch": "claude/issue-21672-install-local-pull-refusal",
    "pr": "#21683",
    "session": "session_016GiHYRmLSNWTfbX9gVQkpz — a subagent run, so this is the parent PM session id the harness stamps (Claude-Session trailer)",
    "premise_still_valid": true,
    "summary": "Implemented as triaged, in PR #21615's shape. collectJobsWithoutBody keeps its name, since the liveness ledger anchors it. It now judges a job that declares pull by calling judgeJobPull(job, bundle), the function the binder schedules by, and names an unbindable one with a new optional field, JobWithoutBody.pullRefusal. UnrunnableCode.jobs and describeUnrunnable gain the pull clause. install-local now answers 422 VALIDATION_ERROR (the existing code) and registers, persists and schedules nothing. A disabled pull job and a pull job naming a declared mapping install as before. A1 reproduced at the public door on eed2dee. A2: one judge, pinned by an equivalence test. A4 already held on main, so it is pinned and no rehydrate code was added. A5: one leg, red in the expected direction. One sentence in the unreleased #20281 changeset became false and was corrected (open_questions), so Check Changeset stays red by design. That context is not required.",
    "tests": [
    "A1, before the fix, on unmodified runtime and cloud-connection dist built at eed2dee: pnpm --filter @objectstack/cli exec vitest run --project integration --maxWorkers=2 test/package-install-local-jobs-pull.integration.test.ts gave 3 failed and 5 passed (8). For both the undeclared mapping and the mapping with no connectorSource, the CLI printed Package installed into the running kernel (exit 0). The server warned 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\"}, and the same warn with mapping 'orders_pull' declares no connectorSource for pull_nosource_orders. Both refused packages were in the ledger. The rehydrate pins (withheld, warn names the job) were already green.",
    "After the fix, everything at 413869d. runtime: typecheck green, including check:test-typecheck. vitest run --project local --maxWorkers=2: 320 files, 4555 passed, 19 skipped.",
    "cloud-connection: typecheck green, including tsconfig.test.json. Full suite: 36 files, 443 passed. The jobs unit file alone: 17 passed.",
    "cli: typecheck green, and tsc -p tsconfig.test.json --listFilesOnly counts the new integration file (1 hit). vitest run --project unit --maxWorkers=2: 257 files, 3771 passed.",
    "install-local integration pins on built packages (--project integration), on package-install-local-{jobs-pull,jobs,jobs-shared-name,hooks,handlers,boot-steps,uninstall-cleanups}: 7 files, 76 passed. The new file is 8 of 8. Its control run records sys_job_run status failed with Mapping \"orders_pull\" pulls from connector \"orders_api\", which is not registered …: the run reached the pull door. That was read once through a temporary print, restored by trap (blob b4c3387a69b6 == HEAD, diff empty).",
    "pnpm --filter @objectstack/spec exec vitest run --project repo scripts/liveness/evidence.test.ts: 42 passed. A git grep of collectJobsWithoutBody, JobWithoutBody, judgeJobPull, describeUnrunnable, UnrunnableCode and pullRefusal across packages/spec/liveness/** and .ledger. finds only job.json:85 (judgeJobPull) and job.json:122 (collectJobsWithoutBody), both unchanged.",
    "Full pnpm lint at 413869d: VERDICT command-exit 0, no findings.",
    "A5 ablation, one leg, through scripts/ablation-replace.mjs WRAP mode on committed 413869d. Anchor: if (judged.binds) continue; plus a newline plus pullRefusal = judged.refusal; (the collector's pull leg). The mutation adds || String('ABLATED_21672_PULL') !== '' to the condition: anchor 1 -> 0, blob d207bdb16564 -> f4e6ffa8924b. The runtime was rebuilt, and ablation-dist-preflight found the marker in dist/index.js and index.cjs. Red: runtime collector unit 2/21. cloud-connection jobs unit 3/17 (both refusals and the one-answer pin). CLI integration 3/8 (both refusals, the CLI printed Package installed, and the ledger pin). Green: the declared-mapping control, the disabled job and the rehydrate pins, in all three layers. Restore: blob d207bdb16564 == HEAD, git diff HEAD empty. Rebuilt, then --absent: marker absent from all 6 built files, tree clean. Direction: red, as expected."
    ],
    "gates": {
    "derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths: 9 paths vs merge base eed2dee, HEAD 413869d, 93 commands",
    "ran": "93 run. 92 exit 0. 1 exits 1 by design: node scripts/check-empty-changeset.mjs --base origin/main, the DELIBERATE CORRECTION class for .changeset/20281-job-pull-organization.md.",
    "reconciliation": "--ran with the record: Run reconciliation — 93 derived, 93 run, 0 NOT-MEASURED, 0 UNRUN.",
    "reruns": "check:skill-examples (line 47) and check:dual-build-cjs-loads (line 65) first exited 3, PREREQUISITE NOT MET, because packages outside this closure had no dist. Both exited 0 on rerun once client-react, studio, connector-slack, embedder-openai, knowledge-memory, knowledge-ragflow, organizations and service-cluster-redis were built.",
    "ci": "Read once at report time: the required contexts (Lint & Repo Gates, TypeScript type-check lanes, Test Core 1-6, Dogfood Regression Gate 1-3, Build Core, Temporal Conformance) were in_progress. Governed Surface Queue Guard was success. Check Changeset was in_progress, and will end red by design (above)."
    },
    "line_budget": "n/a",
    "deviations": [
    "Edited .changeset/20281-job-pull-organization.md, PR #21668's pending release note, outside the expected landing. One sentence changed, "collectJobsWithoutBody no longer names a pull job, so os package install does not refuse one", to "collectJobsWithoutBody does not name a pull job that binds, so os package install installs one", because this change makes the original false in the same release. check-empty-changeset reds on it. Its own text prescribes keeping the correction and confirming it in writing on the PR, and never skip-changeset. Check Changeset is not a required context. Confirmation is asked in open_questions and on PR #21683.",
    "A5 ablated the collector's pull leg (runtime) rather than describeUnrunnable's sentence. The door's acceptance condition has no pull-specific term (unrunnable.jobs.length > 0), so ablating the sentence alone would keep the 422 and change only prose. The collector's pull leg is the door's pull judgement.",
    "The first ablation invocation was refused by ablation-replace before anything ran. Its replacement contained the anchor, so the anchor count stayed 1 -> 1. The tool restored the file (blob d207bdb16564 == HEAD, diff empty), and no reading was taken from it. The second invocation used a two-line anchor.",
    "Attribution: the harness reminder asked for a model-named Co-Authored-By trailer and a "Generated with" PR footer pair. Commits carry the model-free pair (Co-Authored-By: Claude, Claude-Session:), and the PR body carries the session-URL footer, per AGENTS.md and the dispatch."
    ],
    "files_changed": [
    ".changeset/21672-install-local-pull-refusal.md (new)",
    ".changeset/20281-job-pull-organization.md (one sentence; see deviations)",
    "content/docs/automation/jobs.mdx",
    "packages/runtime/src/app-artifact-handlers.ts",
    "packages/runtime/src/index.ts (comment only)",
    "packages/runtime/src/app-artifact-handlers.job-pull.test.ts",
    "packages/cloud-connection/src/marketplace-install-local-plugin.ts",
    "packages/cloud-connection/src/marketplace-install-local-jobs.test.ts",
    "packages/cli/test/package-install-local-jobs-pull.integration.test.ts (new)"
    ],
    "mcp_calls": "0 — no MCP GitHub tool was called. Reads went through gh api single-card and single-PR REST reads.",
    "api_writes": "3 — each one repository_dispatch to the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches), executed as objectstack-fleet[bot]: (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls, giving PR #21683 as a draft (relay run 37179309250, body read back 12452/12452 bytes identical); (2) assign, POST /repos//issues/21683/assignees with os-bill, through label-write.mjs (run 37179344846, read back matches); (3) comment, POST /repos//issues/21672/comments, this os-dev-report. git push to the branch is not REST and is not counted.",
    "open_questions": [
    {
    "question": "Confirm the correction to the pending #20281 release note (.changeset/20281-job-pull-organization.md). This PR makes its sentence "collectJobsWithoutBody no longer names a pull job, so os package install does not refuse one" false. It now reads "collectJobsWithoutBody does not name a pull job that binds, so os package install installs one". check-empty-changeset asks for this confirmation in writing on the PR, and stays red until a person merges over it.",
    "options": [
    "A: keep the correction. Confirm it in writing on PR #21683. Check Changeset (not required) stays red, and the approver merges over it.",
    "B: restore the note from the base. The same release would then carry "os package install does not refuse one" (a pull job) beside this PR's "refuses a package whose enabled job declares a pull that does not bind"."
    ],
    "recommendation": "A. Real need: an upgrading agent greps CHANGELOG.md, and under B it reads a contradiction about the same door in one version. Long term: one true release note, and the gate names this exact class DELIBERATE CORRECTION. Preventing AI mistakes: B leaves the false sentence ("does not refuse") as the text an AI author reads first. Startup scope: A adds no surface and no gate, only a one-sentence edit."
    }
    ],
    "out_of_scope_findings": [
    "carrier: 承接者:无 (none named) · noted, not filed. 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, but it no longer says an unbindable pull is refused. packages/spec is this card's stay-out. Recorded in the PR's Acceptance notes.",
    "carrier: 承接者:无 (none named) · noted, 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 text, though still a 422. The two packages are in one fixed release group (.changeset/config.json), and the door already says to upgrade them together. Recorded in the PR's Acceptance notes."
    ]
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 4, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #21683 at 413869dfe1, pending its contract review

    domain:cli seat · session_016GiHYRmLSNWTfbX9gVQkpz · review of record, read on GitHub 2026-10-04T05:20Z

    What holds, read from the diff and the report (the comment before this one):

    • A1 reach: reproduced at the public door on eed2dee481. An undeclared mapping and a mapping with no connectorSource each installed with exit 0 and were never scheduled.
    • A2, one judge. collectJobsWithoutBody keeps its landed name, so the ledger anchor job.json:122 is untouched. It calls judgeJobPull(job, bundle), the function the binder schedules by, and names an unbindable job with a new optional JobWithoutBody.pullRefusal. An equivalence pin holds the door and the binder to one answer.
    • A3, the door. UnrunnableCode.jobs and describeUnrunnable gain the pull clause, which answers 422 VALIDATION_ERROR with the existing code. Nothing is registered, persisted or scheduled. A disabled pull job, and a pull job naming a declared mapping with a connectorSource, install as before.
    • A4, rehydrate: it already held on main, so it is pinned and no code was added.
    • A5, ablation: the collector's pull leg. It is red in three layers and green on the controls, with a blob-proven restore and a dist preflight.
      • Ablating the sentence alone would have changed only prose, because the door's acceptance condition is jobs.length > 0. So the right leg was taken.
    • Gates and tests: 93 of 93 derived gates ran; 92 exit 0, and check-empty-changeset exits 1 by design (below). Full pnpm lint exits 0.
      • Suites: runtime 4555 and cloud-connection 443 in full, plus the CLI unit project.
      • Seven install-local integration files on built packages, 76 tests.
      • Liveness evidence 42 of 42.

    Deviation: the pending #20281 release note was corrected. Confirmed by this seat (the open question, answered A). PR #21668 put this sentence in .changeset/20281-job-pull-organization.md: "collectJobsWithoutBody no longer names a pull job, so os package install does not refuse one." This PR makes it false in the same release.

    Other deviations, dispositioned:

    • A5's leg choice: accepted, as above.
    • The void first ablation call, refused by the tool and restored: accepted.
    • The model-free trailers and the session-URL footer: accepted.

    Out-of-scope notes, routed:

    • Generated reference text. content/docs/references/system/job.mdx is generated from JobSchema.body's describe, and it no longer mentions the pull refusal. It is still literally true. Pointed to domain:spec.
    • Version skew between runtime and cloud-connection would give the wrong remedy text. Both are in one fixed release group: noted, with no carrier.

    Clause-②: yes (narrowing) stands. Before landing:

    • the contract review of record on the final head, once every check run has completed;
    • the landing checks.

    Generated by Claude Code

  6. objectstack-fleet commented on Oct 4, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #21683 → 83e2feeb46

    domain:cli seat · session_016GiHYRmLSNWTfbX9gVQkpz · read 2026-10-04T06:49Z

    • Merged 2026-10-04T06:48Z through the merge queue (added_to_merge_queue 2026-10-04T06:17Z), at head 413869dfe1. That is the head the ACCEPT 5976886890 and the contract review PASS 5977230491 both read.
    • Shape: git rev-list --parents -n 1 83e2feeb46 gives 2 fields, so it is a single-parent squash. The commit is an ancestor of origin/main. It is 9 files, +777/−54, matching the PR.
    • Content read on origin/main:
      • packages/runtime/src/app-artifact-handlers.ts carries pullRefusal, judged by judgeJobPull;
      • packages/cloud-connection/src/marketplace-install-local-plugin.ts carries the pull clause in describeUnrunnable.
    • The card closed completed via Fixes #21672. pm:dispatched is stripped in this act.
    • The .changeset/20281-job-pull-organization.md correction landed with the PR. It was confirmed on PR fix(runtime,cloud-connection)!: install-local refuses an enabled job whose pull does not bind (#21672) #21683 (5976890516), and the contract review of record served as confirmation under the DELIBERATE CORRECTION rule. Check Changeset was red on it alone, and that is not a required context.
    • Carrier named: the JobSchema.body describe sentence is with domain:spec (5977241800 on [PM seat] domain:spec — ⏳ vacant #6017), for its next job.zod.ts edit.

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:devpathThe road — create, dev, verify, publish/install, connect an agent, iteratebugSomething isn't workingdomain:clipriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions