Skip to content

runtime: two jobs of one name in one artifact share one job-service key, so the later one silently replaces the earlier and the replaced job never runs (no log line) #32411

Closed

Description

@objectstack-fleet

domain:server#7 · os-justin · session_01QSeKYm8sWjp2Pk76warN7w

Filing gate: ① This defect was measured by the #32355 dev (PR #32410, out-of-scope finding, class a; report 6792591092).

Where: in packages/runtime/src/app-artifact-handlers.ts, jobKeyFor returns the app's existing key for a second job of the same name. CronJobAdapter.schedule and IntervalJobAdapter.store (packages/services/service-job) cancel the existing job and replace it.

What happens:

  1. os build accepts a preserve composition whose manifest-less input carries a job with the same name as the job of the body that carries the artifact's id (measured: exit 0).
  2. At boot both jobs are scheduled under one (app, name) key, and the later one silently wins.
  3. On a real CronJobAdapter, only one of the two pulls ever runs, and nothing is logged. The residual-owner control pin in app-artifact-handlers.job-pull.test.ts (PR fix(runtime)!: a job name a package body declares and the residual carries is refused, never pulled as the body #32410) asserts the one surviving pull.

#32410 refuses the case where the residual's owner is NOT the declaring body. The case where it IS the declaring body still collapses silently.

Do:

  1. Measure on main once fix(runtime)!: a job name a package body declares and the residual carries is refused, never pulled as the body #32410 lands: two jobs of one name in one artifact, both owner arrangements, on a real CronJobAdapter and on IntervalJobAdapter.
  2. Choose one of two fixes:
    • refuse at bind with a coded refusal naming both jobs; or
    • build-side, refuse the composition (os build / composeStacks). This is spec lane; report it if that is where it belongs.
      Never keep one job silently.
  3. Pins:
    • two same-name jobs are refused, naming both;
    • control: one job binds as today.
      Ablate once.

Clause-②: no (narrowing) if a composition that booted is now refused. First-party census expected to be 0.
Business reason: a scheduled pull or job silently never runs, and the operator gets no signal.

Dedupe (job service key collision, residual job same name dropped, jobKeyFor duplicate job name, scheduleAppArtifactJobs one key two jobs): none found.

Activity

  1. added
    bugSomething isn't working
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    domain:serverDomain lane — seat card indexed by label:pm:seat
    on Nov 11, 2026
  2. objectstack-fleet commented on Nov 11, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: finding promoted → domain:server · p2 · pm:blocked · 2026-11-11T09:59Z

    Blocked-by: #32410

    ⛔ Not a claim.


    Generated by Claude Code

  3. added
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Nov 11, 2026
  4. objectstack-fleet commented on Nov 11, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: unblocked → pm:queue · domain:server · p2 · 2026-11-11T12:58Z

    ⛔ Not a claim.


    Generated by Claude Code

  5. added
    pm:queueReady for the PM dispatch loop
    and removed
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    on Nov 11, 2026
  6. objectstack-fleet commented on Nov 12, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-bill · session_01Utp6CaP1iy4RSEQbW6177G

    Claim: PM loop round 5 · 2026-11-12T04:07Z
    Session: session_01Utp6CbjqtQgKYD9snKe5g9 (card thread; seat session_01Utp6CaP1iy4RSEQbW6177G)
    Account: os-bill
    Branch: claude/project-thread-0unrso; measured platform exception to claude/issue-<n>-<slug>
    Worktree: objectstack-issue-32411
    Domain: domain:server
    Seat: domain:server#2
    File surface: packages/runtime/src/app-artifact-handlers.ts, its job tests (app-artifact-handlers*.test.ts), packages/services/service-job/** only if measured necessary, content/docs/** / skills/** sentences this PR makes false, one root .changeset/. ⛔ packages/spec/**, ui/**, docs/adr/**, .claude/**, AGENTS.md. (stop on breach)
    Container & model: S/M, mode:thread, model: default tier (dispatch-gates --tier: no path-derived mandate), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: introduced with per-artifact job keys (jobKeyFor); residual-owner half closed by #32410; no bisect owed (keying gap, not a regression)
    Thread-read: 6796478766
    Premise: holds per the card's measurement (os build exit 0, one pull silently wins); step 0 re-measures on main d8a2f0d2 on both adapters and both owner arrangements before code. Seat preference: refuse at bind (runtime); a build-side refusal is spec lane and comes back as needs_decision.
    Serial constraints cleared: #32410 merged, #32349 (PR #32468) merged.

    Provenance: epic #31527 card routed to domain:server#2 by the epic seat (coordinator, 2026-11-12T02:23Z); the maintainer, verbatim 「server 车道暂时也只处理 p1 或是专题相关的 卡」, 2026-11-12T02:21Z, this project's chat.

  7. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 12, 2026
  8. objectstack-fleet commented on Nov 12, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-bill · session_01Utp6CaP1iy4RSEQbW6177G
    os-dev-report

    {
     "issue": 32411,
     "status": "done",
     "branch": "claude/project-thread-0unrso",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/32563",
     "session": "session_01Utp6CbjqtQgKYD9snKe5g9",
     "premise_still_valid": true,
     "summary": "Measured on main d8a2f0d2 on real CronJobAdapter and IntervalJobAdapter (identical): every artifact with two enabled jobs of one name binds both under one job-service key and keeps the later; residual-not-body: survivor's run refused 409; residual-is-body: alpha_map runs, host_map never, no log; two bodies' body-form jobs and a single-package JSON listing a name twice: one silently dropped. composeStacks accepts A and C, and D reaches the binder with no build, so the refusal is at bind (seat ruling on step 0). scheduleAppArtifactJobs now counts enabled jobs by name (jobNameCollisions); a shared name schedules none of its jobs, lists each in the new AppArtifactJobScheduling.refused, logs one error per name with PackageScopedNameRefusedError (RESOURCE_CONFLICT 409, ambiguous-in-environment) naming every holder and each job's run form, and an earlier-scheduled job of the name is cancelled. Disabled twins are not counted; jobDeclaringPackages answers unchanged (its body walk extracted to bodyJobDeclarations). Clause-②: no (narrowing) per the seat's step-0 ruling; first-party census 0.",
     "tests": "Red first at 0252ae53 on main: `vitest run src/app-artifact-handlers.job-pull.test.ts` Tests 11 failed | 43 passed (54), exactly the 11 new [#32411] cases. Green at 9d82ba29: job-pull + jobs + job-pull-run Test Files 3 passed, Tests 101 passed (101). Ablation once via scripts/ablation-replace.mjs under the verify lock: anchor `if (shared.length === 0) return refusals;` -> `>= 0` (x1->x0, blob f75b12fb3e20 -> b527ef0f59ba), Tests 11 failed | 43 passed (54), CONTROL pins green; restored blob == HEAD f75b12fb3e20, git diff HEAD empty; src imported relatively, no dist leg. Final head 431705c8: `pnpm --filter @objectstack/runtime test` Test Files 581 passed (581), Tests 9896 passed | 19 skipped; `pnpm --filter @objectstack/runtime typecheck` VERDICT command-exit 0.",
     "gates": "Final head 431705c8 (merged origin/main 2359ceadc). dispatch-gates --commands: 97 derived, each run, exit codes captured before any pipe (/mnt/project-files/server-seat2-p2/card-32411/gates-ran-431705c8.txt); --ran: 97 accounted, 95 run, 2 NOT-MEASURED (exit 3 PREREQUISITE NOT MET: `pnpm --filter @objectstack/spec run check:skill-examples` needs built client SDK, `pnpm check:dual-build-cjs-loads` needs a full pnpm build; CI runs both). check-plugin-teardown-shape --self-test first exit 3 on a shallow clone, exit 0 after git fetch --unshallow. pnpm lint exit 0. PR check-runs: in_progress at report time.",
     "dogfood_battery": "431705c8 · VERDICT command-exit 1 · Test Files 1 failed | 450 passed | 3 skipped (454) · Tests 1 failed | 3779 passed | 42 skipped (3822) · the one failure: test/package-commit-revert-path-package.dogfood.test.ts '[#29414] control: a writable package's edit commit restores its own row in place' — Test timed out in 5000ms (package commit revert, no job surface); re-run alone at the same head: Test Files 1 passed, Tests 12 passed (12), VERDICT command-exit 0. Read as a load timeout, not a consequence of this diff.",
     "mcp_calls": "0",
     "api_writes": "2 — fleet-write relay pr_create (run 43158205252, PR #32563, read-back identical); this os-dev-report comment via post-stamped.mjs",
     "open_questions": [],
     "security_findings": "none",
     "out_of_scope_findings": [
      "carrier: none · noted in PR acceptance notes, not filed: the install-local door's up-front refusal (collectJobsWithoutBody, 422) does not judge duplicate job names; such a package installs and the bind refusal logs at error. DbJobAdapter replace-by-name inferred by delegation, not measured separately."
     ],
     "deviations": "Claim Clause-② `no`; PR and changeset carry `no (narrowing)` on the seat's step-0 ruling. Three existing #32071/#32355 pins moved from a run refusal or silent winner to a bind refusal (their jobDeclaringPackages assertions unchanged), and the #32355 fixtures moved to module scope. Serial dependency on #32567 (#32412) announced by the seat after the PR opened: merge main and adapt candidates to the residual holder kind once it lands.",
     "files_changed": [
      ".changeset/32411-same-name-jobs-refused-at-bind.md",
      "content/docs/automation/jobs.mdx",
      "packages/runtime/src/app-artifact-handlers.job-pull.test.ts",
      "packages/runtime/src/app-artifact-handlers.jobs.test.ts",
      "packages/runtime/src/app-artifact-handlers.ts"
     ],
     "line_budget": "495 changed lines (+386 / -109, 5 files) vs the 3000 human-merge threshold"
    }

    Generated by Claude Code

  9. objectstack-fleet commented on Nov 12, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-bill · session_01Utp6CaP1iy4RSEQbW6177G

    ACCEPT → PR #32563 comment 6818147506

  10. objectstack-fleet commented on Nov 12, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-bill · session_01Utp6CaP1iy4RSEQbW6177G

    Landed — PR #32563

    Reading Value
    PR #32563 MERGED 2026-11-12T08:28Z, merge commit 1a17e96a2115fe5cf946639a1bdcd4004487acb0
    #32411 closed by the PR's Fixes #32411

    State: pm:dispatched removed; card closed. Follow-up: #32572 (install-local answers 200 for twins). Nothing else is left on this card.

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions