Skip to content

fix(runtime,cloud-connection)!: install-local refuses an enabled job whose pull does not bind (#21672) - #21683

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-21672-install-local-pull-refusal
Oct 4, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-21672-install-local-pull-refusal

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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

claude added 3 commits October 4, 2026 04:19
… 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
@github-actions github-actions Bot added the size/l label Oct 4, 2026
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Oct 4, 2026
@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/cloud-connection, @objectstack/runtime, touching 7 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/runtime/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/deployment/cli.mdx (via MarketplaceInstallLocalPlugin (symbol, a top-level class))
What this run could not see
  • 1 changed file(s) yielded no anchor (packages/runtime/src/index.ts) — pages documenting those are invisible to this run
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 28 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 7d0781482dbb6502aa33c29bec96ca03636f7df9 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 76e15c6c1fbef88a242c0f6bfa18c80c6b1ecb8e — the merge of head 413869dfe1121301bbe00bfa4860cd9cf32cf258 into base 7d0781482dbb6502aa33c29bec96ca03636f7df9, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 7d0781482dbb6502aa33c29bec96ca03636f7df9 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Release-note correction, confirmed (check-empty-changeset, class DELIBERATE CORRECTION) · domain:cli seat, reviewer of record · session_016GiHYRmLSNWTfbX9gVQkpz · 2026-10-04T05:20Z


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 413869dfe1121301bbe00bfa4860cd9cf32cf258
Local-runs: none

① Derived judgments

Inputs read. Card #21672: the filing, triage grade 5975994778, unblock 5976336256, the seat's claim 5976423110, the os-dev report 5976872367 and the seat's ACCEPT 5976886890. PR #21683: body, file list (9 files), the net diff against main from merge base eed2dee481 (no commit on main since then touches a PR path), and the two PR comments. The 35 check-runs on the head, all completed, one per name. Ruleset 12119582 on main. Precedent PR #21615 (045b946256), with PR #21668 (909229e976) as background.

Accept-set changes.

  • The install-local door (MarketplaceInstallLocalPlugin, POST /api/v1/marketplace/install-local, os package install) now refuses a package whose ENABLED job declares a pull that judgeJobPull does not bind: a mapping the package does not declare, a mapping with no connectorSource, or body/handler beside the pull. The answer is 422 VALIDATION_ERROR, the code and status the door already gives a body that does not bind; UNRUNNABLE_REFUSAL_CODE and the status constant are unchanged and no error code is added. Right. This is a narrowing of the door's accept set, closing the card's measured defect (200, a warn, no schedule) in PR fix(runtime,cloud-connection)!: install-local refuses a hook with no body and a job body that does not bind, and withholds such a hook on rehydrate (#21585) #21615's shape.
  • A DISABLED unbindable pull job still installs: the collector skips enabled === false before judging, as the binder does. A pull job naming a declared mapping with a connectorSource still installs and is scheduled. Right — both controls are pinned in all three layers (runtime unit, cloud-connection unit, CLI integration at the public door).
  • The boot door (os start --artifact) and rehydrate: unchanged (below). Right.

Public-surface changes.

  • @objectstack/runtime JobWithoutBody gains one optional field, pullRefusal. Additive. A job carrying it carries no bodyRefusal: the collector judges pull first and body only in the else branch, the binder's own order. Right.
  • @objectstack/runtime collectJobsWithoutBody now names an enabled pull job exactly when judgeJobPull refuses it, and never one that binds. Its only non-test consumer on main is the install-local door. Right.
  • @objectstack/cloud-connection: the module-private UnrunnableCode.jobs gains pullRefusal; describeUnrunnable gains one pull clause, and its three filters are disjoint, so a job with a pullRefusal never falls into the no-body or bad-body clause. Right — the remedy never sends the author to write a body beside a pull, the shape the declaration refuses. The clause carries no tracker number and names os validate as the authoring-time twin.
  • packages/runtime/src/index.ts: a comment only. packages/spec, service-automation, objectql, JobSchema, MappingSchema, the error-code ledger: untouched, as the claim's stay-outs required.

One judge. The collector calls judgeJobPull(job, bundle) (app-artifact-handlers.ts, the collector body) and the binder scheduleAppArtifactJobs calls judgeJobPull(job, bundle) before it schedules a pull job: the same function, the same two arguments, reading the same collectBundleJobs and collectBundleMappings. The door hands the same manifest object to collectJobs(manifest) and to bindArtifactHandlers(ctx, manifest, manifestId), and carries no mapping lookup of its own: the only connectorSource in the door file is refusal prose. describeUnrunnable formats the pullRefusal it is handed and re-judges nothing. No second judge; the claim's stop condition held. The runtime equivalence pin (the named set equals the binder's notScheduled minus the handler job, and each refusal is the tail of the binder's warn) is the right pin for it.

Landed names. collectJobsWithoutBody and JobWithoutBody keep their names. packages/spec/liveness/job.json is byte-identical between main and the head; its judgeJobPull row and its collectJobsWithoutBody row still resolve. Spec property liveness is green on the head. Right.

Rehydrate unchanged, as claimed. The binder's function body is not in the diff: the hunks in app-artifact-handlers.ts are the module header, the JobWithoutBody type, the collector and TSDoc. The door's rehydrate path (bindArtifactHandlers(ctx, entry.manifest, …)) is not in the diff, and the door's install gate sits before persistence while rehydrate is not gated. The binder's pre-existing skip-and-warn withholds an unbindable pull job of a persisted entry; the CLI second-boot pins and the cloud-connection rehydrate unit pin it, and the report says they were green before the fix. Right — "pinned, not coded" is the correct disposition.

Docs. content/docs/automation/jobs.mdx now says a pull job installs when its pull binds and is refused with the same 422 when it does not, and what a restart does with an entry an earlier version installed. True to the diff. The generated content/docs/references/system/job.mdx is not touched; see ③.

Gate verdicts on the head. 35 runs, all completed, one per name. The seven contexts ruleset 12119582 requires on main are all success: TypeScript Type Check, Test Core, Dogfood Regression Gate, Build Core, Temporal Conformance (live PG + MySQL), Lint & Repo Gates, Governed Surface Queue Guard. Console Pin Gate and Packed-tarball smoke (opt-in) are skipped by their own conditions. Check Changeset is the one failure, judged in ②.

② Semver level

  • .changeset/21672-install-local-pull-refusal.md names @objectstack/runtime: minor and @objectstack/cloud-connection: minor. Those are exactly the two released packages whose source the diff changes; the packages/cli change is a test file only, and content/docs publishes nothing. Matches the diff. No skip-changeset.
  • Clause-②: yes (narrowing) in the PR body and in the changeset body. A narrowing is BREAKING: the title carries !, the body carries BREAKING, the before (installed with 200, never scheduled) and after (422 with the remedy), and the route out (declare the mapping with a connectorSource, or correct the pull). minor under the launch-window convention for a narrowing of an accept set, the shape PR fix(runtime,cloud-connection)!: install-local refuses a hook with no body and a job body that does not bind, and withholds such a hook on rehydrate (#21585) #21615 landed with; the job's Guard against accidental major bumps (launch window) step passed. Right.
  • ADR-0087 marker: exactly one, not-required (no-migration-prescription), closing the other categories on facts (schemas unchanged, so nothing to migrate; the packages publish; no ADR-0087 id covers a refused install; runtime behaviour, not a declaration). The same disposition as the precedent. The job's Require an ADR-0087 disposition on a declared-breaking changeset step passed and emitted it as a notice. Right.
  • Check Changeset, red, judged independently of the seat:
    • Only cause. The job's step list shows one failure, step 12 Reject an empty-frontmatter changeset added by this PR (check-empty-changeset), and the one file-level failure annotation is on .changeset/20281-job-pull-organization.md under the foreign-changeset refusal. Steps 11 (a changeset is present), 13 (the ADR-0087 disposition) and 15 (the major-bump guard) each success. So that refusal is the only cause.
    • The correction is true. The diff changes one sentence of PR feat(spec,runtime,service-automation): a job pulls a mapping by declaration (pull: { mapping }) and runs as its declared organization (#20281 stage 3) #21668's pending note, from "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". After this diff the first sentence is false and the second is true: the collector names only a pull job judgeJobPull refuses, and a binding one installs. The frontmatter and every other line of the note are byte-identical. Restoring it from the base would ship a false sentence about this door in the same release as this PR's own note. The gate names this class DELIBERATE CORRECTION; its remedy is "do NOT restore it — say so on the PR and get it confirmed", never skip-changeset. The PR body says so, and comment 5976890516 confirms it in writing with the note's owning seat pointed at it.
    • Not a required context. Ruleset 12119582's required_status_checks on main lists the seven contexts named in ① and not Check Changeset. Read off the ruleset, not taken from the seat.
    • Residual, named rather than hidden: the gate's own text calls the confirmation "the existing human path", and the confirmation of record here is the owning seat's. No rule in AGENTS.md reserves a pending-note correction to the maintainer; writing changesets is release-adjacent work open to every seat. The red stays visible to whoever lands the PR, which is what the gate says it is for.

③ Boundary flags

Dev deviations (report 5976872367), each answered:

  1. Edited the pending PR feat(spec,runtime,service-automation): a job pulls a mapping by declaration (pull: { mapping }) and runs as its declared organization (#20281 stage 3) #21668 note. Answered in ②: true, minimal, confirmed on the PR; keep it.
  2. A5 ablated the collector's pull leg rather than describeUnrunnable's sentence. Right. The door's acceptance condition is that unrunnable.jobs.length or unrunnable.hooks.length is non-zero, with no pull-specific term, so the pull judgement lives in the collector; ablating the prose would leave the 422 standing. Red in three layers (runtime 2 of 21, cloud-connection 3 of 17, CLI 3 of 8) with the declared-mapping control, the disabled job and the rehydrate pins green; blob-proven restore; dist preflight present and then absent. Accepted.
  3. The void first ablation call: refused by the tool before anything ran, restored, no reading taken. Accepted.
  4. Model-free trailers. The three commits end with the Co-Authored-By: Claude and Claude-Session: pair; that is AGENTS.md's rule and the pre-push hook's, and it outranks the harness reminder. Right.

open_questions (one): confirm the correction, A keep or B restore. A, for the reason in ②. The dev's recommendation and the seat's ruling agree; nothing is left open.

out_of_scope_findings (two):

  1. JobSchema.body's describe in packages/spec/src/system/job.zod.ts, and so the generated content/docs/references/system/job.mdx, still says "os package install therefore refuses an enabled job with no body (a pull job excepted: it is data too)". Literally true of the no-body clause, but its plain reading, that a pull job is never refused at install, is false after this PR. packages/spec is this card's stay-out, so not editing it here is right. Escalated to domain:spec, which the seat's ACCEPT pointed but with no carrier named: the sentence should say a pull installs when it binds and is refused when it does not, in the next PR that touches job.zod.ts, with the spec regeneration pass carrying the mdx. Not blocking: defineStack, os validate and the install refusal each carry the prescription, so no author is trapped silently.
  2. Version skew (a newer runtime behind an older cloud-connection describes an unbindable pull with the no-body remedy text, still a 422). Bounded: both packages sit in the one fixed release group in .changeset/config.json, and the door's own warn tells an operator to upgrade them together. Answered; nothing to file.

Governance. The file list is .changeset/, content/docs/ and packages/ only; no governed surface is touched and Governed Surface Queue Guard is green. Landing is the queue's, on the required contexts, after this record; the one red context is not among them.

Implemented-by: claude/issue-21672-install-local-pull-refusal
Reviewed-by: session_016GiHYRmLSNWTfbX9gVQkpz

VERDICT: PASS


Generated by Claude Code

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

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

2 participants