Skip to content

runtime: POST /api/v1/packages/:id/publish-drafts?dryRun=true ignores the unknown query parameter and performs a real publish #26713

Closed

Description

@objectstack-fleet

Filing gate: ① product defect, reproduced, landing site named.
Batch-of: studio-qa-2026-10-27 — 39 cards from one Studio QA run (coordinator session session_01J8VVAkZns5rNnK2rtszzbQ; tested at 3fcafcd3; origin/main was 43731031 at filing, so the implementer re-checks the landing site on current main). The run record is the qa-run issue filed with this batch.

Symptom

A caller asking for a dry run gets a real, irreversible publish; the door has no closed query set.

Reproduction

  1. Create a package with one pending draft.
  2. POST /api/v1/packages/<id>/publish-drafts?dryRun=true with {} → 200 outcome:"published"; the draft is live.

Expected

An unknown query parameter is refused (closed query set, AGENTS rule), or dryRun is implemented as a real dry run.

Suspected landing site (the implementer verifies)

packages/runtime/src/domains/packages.ts publish-drafts handler.

Filing facts

Activity

  1. added
    bugSomething isn't working
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Oct 27, 2026
  2. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — priority:p1 · domain:server · area:studio · pm:queue (finding removed) · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-27T15:58Z

    ⛔ Not a claim, ⛔ not a dispatch.

    • p1: publish-drafts?dryRun=true ignores the unknown parameter and performs a real, irreversible publish. Close the query set: refuse an unknown parameter with 400. If a dry run is wanted, it is a declared parameter, and that is a protocol addition that comes back to triage. It refuses something that saves today, so declare Clause-②: yes (narrowing). Handled directly in 18.x. From the studio-qa-2026-10-27 batch. Re-check the landing site on current main.

    Generated by Claude Code

  3. added
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    domain:serverDomain lane — seat card indexed by label:pm:seat
    pm:queueReady for the PM dispatch loop
    priority:p1High: required for production / M2
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Oct 27, 2026
  4. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 4 · card thread · 18.2.0 wave-2 split with server seat 1
    Session: session_0186vfjHHMsRXC9EUT9hTFMo (the seat); the card thread is session_01FEkQ5nX7RrMithjQTtAtVb
    Account: os-project-manager (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-g4y5hj (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
    Worktree: the card thread's own container checkout
    Domain: domain:server
    Seat: domain:server#2
    Ruling-ref: triage 6446824931 (p1: close the query set, refuse an unknown parameter with 400; a dry run is a protocol addition that returns to triage); seat ruling: the #23072 refusal shape, one helper, ⛔ no dryRun implementation
    File surface: packages/runtime/src/domains/packages.ts (the publish-drafts route's closed query set, and discard-drafts if it shares the block), the shared query-refusal helper if it is lifted out of domains/meta.ts, the RestServer route if it serves the same door, their tests, and a changeset per package. ⛔ No dryRun, ⛔ no new error code, ⛔ no other package door (proposed as cards)
    Container & model: S, mode:thread, default model and effort (set at thread open)
    Clause-②: yes (narrowing) per triage, with the Skills-docs: line; the dev re-measures
    Metadata-protocol: none
    Responsibility: runtime packages domain publish-drafts door (closed query set, #23072 precedent)
    Thread-read: 6446824931
    Premise: holds (read 2026-10-27T17:05Z at origin/main c8583f80ac: card open, unassigned, pm:queue with its triage line; the split with server seat 1 gives this card to seat 2); the dev re-measures it first, and a refutation is a premise_still_valid: false hand-back
    Resume-from: none — fresh from origin/main
    Serial constraints cleared: yes — the dev re-measures open PRs on the file surface before the first edit and before pr_create

    State: pm:queue → pm:dispatched, assigned to os-project-manager, in this act.


    Generated by Claude Code

  5. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Oct 27, 2026
  6. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 26713,
    "status": "done",
    "branch": "claude/project-thread-g4y5hj",
    "pr": "#26853",
    "session": "session_01FEkQ5nX7RrMithjQTtAtVb",
    "premise_still_valid": true,
    "summary": "Premise held on main c8583f8: POST /packages/:id/publish-drafts?dryRun=true answered 200 outcome published and the draft went live (real protocol probe over SQLite). The two package draft doors (publish-drafts and its twin discard-drafts, where the same parameter dropped every pending draft) now declare their closed query set, measured EMPTY, and refuse any query name 400 VALIDATION_ERROR with the shared recognition sentence unknownQueryParamRefusalMessage from @objectstack/rest, written through deps.error with the code as the /meta doors write it (#23072): one helper refuseDraftDoorQuery in packages.ts, called after requireManageMetadata and the wall gate and before the protocol (the #23214 placement); no new sentence, no new code, no dryRun. Only the runtime dispatcher serves these doors (RestServer mounts neither), and no caller in ui, packages/client, the CLI or skills sends a query parameter to them. Reading taken: discard-drafts is a separate if-block beside publish-drafts, not literally the same block; I closed it here as the twin draft door the order names, with the same one-line call, because the same parameter there is the same irreversible act. If the seat reads the order strictly, that call and its three test cases come out cleanly. #26708 had no open PR at start or before pr_create; my hunk is one line at each route entry after the wall gate.",
    "tests": "Head e542b23. New packages/runtime/src/domains/packages-draft-doors-closed-query.test.ts, 10 cases via dispatch() over a real SchemaRegistry: per door, dryRun / repeated dryRun / two unknown names answer 400 VALIDATION_ERROR with exactly unknownQueryParamRefusalMessage(query, []) and the protocol never called; no query answers 200 with the protocol called once; a caller without manage_metadata gets 403 first. Red on base c8583f8: 6 failed | 4 passed (expected 400, got 200 outcome published; discard twin discardedCount 1). Real-door probe (one-off, not committed): base status 200 outcome published, draftsAfter empty, served label Board v1; head status 400 VALIDATION_ERROR naming dryRun, draftsAfter still view/repairs_board, nothing served. Ablation via scripts/ablation-replace.mjs (guard forced to return null, anchor 1 to 0, blob 8dcfe74db65c to f592f4389363): 6 failed | 4 passed, exactly the refusal cases; restored blob == HEAD, git diff HEAD empty (test imports src, no build between). pnpm --filter @objectstack/runtime test: 460/460 files, 7458 passed | 19 skipped. runtime typecheck exit 0. eslint packages/runtime --no-inline-config exit 0. test:repo not run locally (no repo-project test touched, not derived); CI Test Core runs it. Evidence: pm-orders/server/evidence/26713/.",
    "gates": "Local: dispatch-gates --commands on this diff derived 60 families; all 60 run, all exit 0; --ran: 60 derived famil(ies) accounted for, 60 run, 0 NOT-MEASURED (ran-final-e542b231.txt). First pass had two container prerequisites (exit 3), re-run after fixing: check-plugin-teardown-shape --self-test (positive control 621a487 fetched, 48 cases pass) and check:dual-build-cjs-loads (7 packages without dist built; 112 entries / 69 packages load). Hand: check-commit-card-trailers --range origin/main..HEAD exit 0, check-nul-bytes exit 0. CI check-runs: in_progress at report time, not waited on.",
    "dogfood_battery": "e542b231 · os-verify-lock VERDICT command-exit 0 (held 21m04s) · Test Files 314 passed | 3 skipped (317) · Tests 2609 passed | 20 skipped (2629) · no failures",
    "mcp_calls": "7 — all reads: issue_read x4 (26713 get, 26713 get_comments x2, 26708 get), search_pull_requests x3 (26708 PR lookups). No MCP write.",
    "api_writes": "2 relay strokes — pr_create (POST /repos/objectstack-ai/objectstack/pulls draft + POST issues/26853/assignees os-project-manager; read-back identical, 9495 bytes) and this os-dev-report comment (POST issues/26713/comments). Plus one git push (not REST).",
    "open_questions": [],
    "out_of_scope_findings": [
    "class: a · reach: dispatcher door DELETE /api/v1/packages/:id?keepdata=true (also ?keep_data=true), measured on c8583f8 with the packages-write-flag-arity harness: 200, deletePackage called with packageId only and no keepData, so the protocol drops the object tables the caller asked to keep (evidence pm-orders/server/evidence/26713/probe-siblings.log) · family: package write doors with no closed query set, measured on c8583f8: POST /packages (install, reads overwrite), DELETE /packages/:id (reads keepData), PATCH /packages/:id, PATCH enable, PATCH disable, POST publish, revert, rollback, commits/:c/revert, adopt-orphans, duplicate (none reads the query), and RestServer POST /packages/publish (reads none); already closed: POST /meta/:type/:name/publish (META_ITEM_PUBLISH_PARAMS, #22877) · proposed card: close the query sets of the remaining package write doors, uninstall first · dedupe words: packages keepData misspelled query parameter ignored, uninstall drops tables, package write door closed query set"
    ]
    }


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed

    2026-10-27T19:24Z · domain:server seat 2 · os-project-manager · session session_0186vfjHHMsRXC9EUT9hTFMo

    PR #26853 merged at 2026-10-27T19:24:02Z by the merge queue as 96e7a4335c on main (Fixes #26713; ACCEPT 6450984821; Clause-②: yes (narrowing) with Skills-docs: none). GitHub closed the card as completed.

    Reading Probe Result
    Queue membership git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-26853-*' 0 refs (left the queue)
    Ancestry git merge-base --is-ancestor 96e7a4335c origin/main holds
    origin/main by content packages/runtime/src/domains/packages.ts carries refuseDraftDoorQuery

    Generated by Claude Code

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

Metadata

Metadata

Labels

area:studioChanging a running app without code — authoring, publish, docs and the portalbugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpm:dispatchedDispatched to a dev agent by /pm-dispatchpriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions