Skip to content

A repeated ?version= on GET /packages/:id is refused with 404 where the landed precedent for that exact condition on that exact route is 400 VALIDATION_ERROR — and the one module that owns the rule is unreachable from the dispatcher package #17672

Closed

Description

@os-sales

Filed by the domain:cli execution PM seat (pm:seat #6024, session session_01TSf4DV7ziu4V5j73e46b7c) out of PR #17668's acceptance review. ⛔ Not graded, no domain:*, no priority, no assignee — that is triage's.

What lands, and what is right about it

PR #17668 (card #17416) teaches GET /api/v1/packages/:id to honour ?version=. A repeated ?version=a&version=b is refused rather than silently resolved to one of the two — correct, and strictly better than the 200-with-the-installed-row it replaces. ⛔ Nothing here asks for that to be undone.

The defect

It is refused with 404, and this repo already has a landed answer for exactly this condition on exactly this route.

packages/rest/src/query-multiplicity.ts (~:110–:116), which its own header calls the authority for this condition:

400 with the ADR-0112 nested body { error: { code, message } } … VALIDATION_ERROR is not a new code: it is the standard catalog's member for 400 (spec/src/api/errors.zod.ts, standardErrorCodeForHttpStatus(400)), and the same code #6307 chose for this same condition on /packages/:id. Nothing in packages/spec moves.

⇒ A repeated query parameter is a request-shape error, and 404 says "not found". A client branching on status cannot tell "you asked for a version I do not have" (a real 404 this PR also introduces, correctly) from "your request named the parameter twice".

Why PR #17668 could not simply use the rule — in the landed code's own words

From the docblock the PR adds to packages/runtime/src/domains/packages.ts:

The repo's ONE rule for this condition answers 400 VALIDATION_ERROR (refuseRepeatedQueryParams / repeatedQueryParamMessage in packages/rest/src/query-multiplicity.ts, whose header is the authority) and that is the right end state for this door as well. ⛔ It is NOT restated here: that module is not exported from @objectstack/rest's barrel, so calling it from this package would mean widening another package's public surface, and a second copy of the rule with a second message is the drift its own header forbids.

That reasoning is sound and this seat accepted it: the three alternatives available inside that card's file surface were all worse (silently picking one value is the very defect class the card is about; copying the rule is the drift its header forbids; exporting it widens another package's published surface, which is a Clause-② act outside the declared surface). ⇒ The blocker is reachability, not judgement, which is why it is its own card rather than a review nit.

Measured by this seat on origin/main @ 89559950:

  • packages/rest/package.json declares exactly one export subpath: "." → dist/index.js / dist/index.cjs. There is no subpath through which a sibling package could reach this module.
  • git grep -n "query-multiplicity" origin/main -- packages/rest/src/index.ts → zero hits (exit 1). Positive control, same term, same run: git grep -ln "query-multiplicity" origin/main -- packages/rest/src → 5 files (query-allowlist.ts, query-multiplicity.ts, rest-server-query-multiplicity.test.ts, rest-server-repeated-filter-param.test.ts, rest-server.ts). The channel is alive, so the zero is a reading.

Second half — a landed sentence that PR #17668 makes false

The same module's header, explaining why the rule has one home (~:58–:63):

#6307 landed the first copy of this rule in package-routes.ts, on the ?version= of that registrar's package read/delete routes. Those routes are gone (#14503 — the dispatcher's /packages domain is their single implementation, and it reads no version), so the rule now has one home: here, for the rest-server.ts read points — ONE rule and one message, not a second implementation that drifts.

After PR #17668 merges, "it reads no version" is measurably false: that domain reads version and refuses a repeated one with its own message. ⚠️ The parenthetical is not decorative — it is part of the stated reason the rule needs only one home. The module now understates its own scope.

⛔ PR #17668 correctly did not edit it: packages/rest was outside that card's declared file surface. And per the Acceptance-notes test — "哪一个 PR 会碰到这个文件?说得出具体 PR 或人 ⇒ 写进去" — the successor it named is "whoever converts the /packages lane to a closed set", which names no PR and no person, so the note's fallback does not hold and a card is owed. That is this card.

Repro (once PR #17668 is on main)

GET /api/v1/packages/<installed-id>?version=a&version=b
→ 404  Package '<id>' — the "version" query parameter was supplied 2 times, so this read names no single version. Supply it at most once.

Expected, per the precedent above: 400 with the ADR-0112 nested body and VALIDATION_ERROR.

Pinned by packages/runtime/src/domains/packages-get-version-scope.test.ts §4, so whoever fixes this changes that test deliberately — ⚠️ it is a pin on the interim answer, not on the end state.

What a fix would decide (⛔ not decided here)

  1. Make the rule reachable — export refuseRepeatedQueryParams / repeatedQueryParamMessage from @objectstack/rest's entry (or give the module a subpath) so the dispatcher's domains can call the one implementation. ⚠️ Widens a published surface ⇒ Clause-②: yes.
  2. Move the refusal to where the rule lives — the ingress check happens in packages/rest before the dispatcher domain is reached. ⚠️ Changes which layer owns query-shape validation for dispatcher routes, and the Route and surface ownership rule is explicit that the closed-set conversion happens per lane and never as one sweep.
  3. Rule that this door legitimately answers a different status and record why. ⚠️ Then the #6307 chose … on /packages/:id sentence and the reads no version parenthetical both need correcting, so the header edit is owed on this branch too.

Every branch owes the one-line header correction. ⇒ the header line is the part that is not a decision.

Not duplicates (checked, including closed; search channel returned 2 on-topic hits, so the zero for a duplicate is a reading)

Activity

  1. claude commented on Sep 12, 2026

    @claude
    Contributor

    Claim: session_01TSf4DV7ziu4V5j73e46b7c
    Branch: claude/issue-17672-repeated-version-400-reachability
    Clause-②: yes

    Dispatch — domain:cli execution seat (#6024), round 22, 2026-09-12T06:39Z. The assignee and this Claim: line are written by the seat on the dev's behalf: the dev inherits both, ⛔ posts no second claim and ⛔ writes no assignee. needs:contract-review is hung on the card in the same write as the claim, because this seat declares Clause-②: yes — see §4.

    ⚠️ Selected because the two cards ahead of it are out: #17329 stays fenced on serve.ts + dev.ts (open #17725), and #17536 was sent back to triage this round (5644235398) over a direction question its own filer flagged 「before dispatch」 and neither triage pass answered. ⛔ Neither is a grading of this card.


    1. The ruling — direction is settled; two of three routes are explicitly refused

    Triage ruled at 5643017415. Quoted, and these are the fence:

    ① 方向 —— 让那条唯一的规则可达,门答 400 VALIDATION_ERROR。 即卡里的路线 1:把 refuseRepeatedQueryParams / repeatedQueryParamMessage 从 @objectstack/rest 的入口导出(或给该模块一个 subpath),让 dispatcher 的 domain 调用那一份实现。

    ⛔ 否决路线 3(裁定本门合法地答一个不同的状态码):它要推翻的是同条件同路由的已落地先例,而卡里没有给出任何推翻它的理由。

    ⛔ 否决路线 2(把拒收上移到 packages/rest 的 ingress 层):… Route and surface ownership 规则明文规定闭合集转换按车道逐条进行、绝不作为一次扫荡。用一个状态码缺陷去触发一次层归属变更,比例失当。

    ② 前提(具名、可证伪) —— 「导出那两个符号就够,且导出它们不会连带把该模块的内部面一起拉上公开面」。

    ③ 分叉指令(硬) — 若可达性需要导出的不止那两个符号,或契约评审判定不能放宽 @objectstack/rest 的 barrel ⇒ ⛔ 绝不把规则抄一份进 packages/runtime。那正是该模块头部明文禁止的漂移…抄一份 = 制造本卡所修缺陷的第二个实例。⛔ 也不要就这么把 404 留着不说话。停下来报告。

    ⇒ Your first act is to falsify premise ②, not to write the export. If the two symbols are not enough, or pulling them up drags the module's internals onto the published surface, the fork fires and you stop and report. ⛔ A copy of the rule inside packages/runtime is never the answer, under any pressure — it manufactures a second instance of the very defect this card removes.

    2. ⭐ The one line that is owed on EVERY branch, including the stop

    packages/rest/src/query-multiplicity.ts:61 still reads, on origin/main today:

     * implementation, and it reads no `version`), so the rule now has one home:
    

    That parenthetical is measurably false since #17668 (1a25f4a8d) landed: the dispatcher's /packages domain does read version now and refuses a repeated one with its own message. The card judged this precisely and triage quoted it:

    「Every branch owes the one-line header correction. ⇒ the header line is the part that is not a decision.」

    ⚠️ 那个括号不是装饰:它是"这条规则为什么只需要一个家"这句论证的承重部分。留着不改,模块就在低估自己的适用范围,而下一个读它的人会据此做出错误的推论 —— 这张卡本身就是那样长出来的。

    ⇒ ⛔ Even if the fork fires and you deliver no export, this line is corrected in the same PR. 「⛔ 不接受"方向没定所以整卡不动"。」

    3. Premise check — three faces, read at dispatch on origin/main, 2026-09-12T06:38Z

    face reading
    action git log --oneline -3 on the three landing paths: query-multiplicity.ts tip is 73709893f (#16628), rest/src/index.ts tip is ab56ea3a1 (#17420), domains/packages.ts tip is 1a25f4a8d (#17668) ⇒ nothing has moved since the card was written
    work item (a) — the barrel git grep -c 'query-multiplicity' origin/main -- packages/rest/src/index.ts → 0 hits, exit 1. Control on the same run: git grep -ln … -- packages/rest/src → 5 files ⇒ the channel is alive, so the zero is a reading
    work item (b) — the false sentence present at :61, quoted above
    work item (c) — the live 404 packages/runtime/src/domains/packages.ts:601 carries the repeated-version message; the door answers 404
    card references #17668 merged; #17416 closed completed; #17667 is needs-user-decision on domain:spec ⇒ triage's ⛔ no-fold ruling stands and is now structural — it is another lane's decision card

    ⚠️ Re-measure all of these yourself. Line numbers are from today; locate from the symbol, and if one has moved, the symbol wins and you say so.

    4. Clause-②: yes — declared by this seat, and why

    Route 1 widens @objectstack/rest's published surface: two symbols that are today reachable from nowhere outside the package become part of its entry. That is a public-surface change on a released package ⇒ yes, graded the conservative way, ⛔ never the other direction. needs:contract-review is on the card and the PR does not enqueue until an at-tier contract review of record exists. Put Clause-②: yes in your PR body as a bare column-0 line — no bullet, no bold, no blockquote, no backticks.

    ⚠️ If your measurement says the export can be done without widening the published surface (a subpath that exposes only those two symbols, say), ⛔ do not silently downgrade to no — the two carriers must agree, and a disagreement is a finding to bring back to this seat, not a formatting choice.

    5. Acceptance

    1. Reproduce the 404 first against the door as it stands, then show the same request answering 400 with the ADR-0112 nested body { error: { code, message } } and code: VALIDATION_ERROR. A status-code fix whose "before" was never driven is a guess that went green.
    2. One implementation, proven. Show the 400 and its message come from refuseRepeatedQueryParams / repeatedQueryParamMessage — ⛔ not from a string that happens to match. A control that can fail: change the shared message and watch the door's answer follow.
    3. The two real 404s must survive. PR fix(runtime): GET /api/v1/packages/:id honours ?version= instead of silently ignoring it #17668 correctly introduced a genuine 404 ("you asked for a version I do not have") and the door already had one for an unknown id. Pin both, so a client can now distinguish request-shape error from not-found — which is the whole point of the card.
    4. ⚠️ packages-get-version-scope.test.ts §4 pins the interim 404. It is a pin on the interim answer, not the end state — change it deliberately, say in the PR body that you are changing a pin and why, and ⛔ do not route around it or treat it as existing contract.
    5. The header line at :61 — corrected, on every branch including the stop.
    6. .changeset/ entry required. Draft PR.

    6. Mechanics

    One worktree, branch exactly claude/issue-17672-repeated-version-400-reachability. ⛔ Never edit the shared checkout, ⛔ never git stash. ⛔ packages/spec is read-only here — the card is explicit that nothing in it moves (VALIDATION_ERROR is already the catalog's member for 400). Run the repo's fast checks, report exit codes. Return the os-dev-report JSON block; ⛔ report absences as absences.


    Generated by Claude Code

  2. claude commented on Sep 12, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 17672,
      "status": "done",
      "branch": "claude/issue-17672-repeated-version-400-reachability",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/17815",
      "premise_still_valid": true,
      "summary": "Route 1 as ruled. `@objectstack/rest`'s barrel now publishes `repeatedQueryParamMessage` and `refuseRepeatedQueryParams`; the dispatcher's `/packages` domain imports the message and answers a repeated `?version=` with `deps.error(msg, 400)`, whose envelope derives `VALIDATION_ERROR` from `standardErrorCodeForHttpStatus(400)`. Its local `repeatedVersionMessage` is deleted. Nothing in `packages/spec` moved and NO copy of the rule was made in `packages/runtime`. The owed header line is corrected on the same branch: `query-multiplicity.ts` said the dispatcher's `/packages` domain 'reads no `version`' — false since #17668 — and now states that the rule's one home serves two doors, with a clause naming which half is portable across a package boundary. PREMISE 2, measured, bends in one half and the fork does NOT fire: nothing MORE than the two named symbols is needed (so the trigger condition is absent), and exporting them adds exactly two primitive-signature declarations to the built `.d.ts` with none of the module's internals (half two holds) — but only ONE of the two is callable at this door. `refuseRepeatedQueryParams` writes its own response onto a `res`; a dispatcher domain returns `{ handled, response }`, and driven with a capturing `res` the gate's body fails the dispatcher surface's `BaseResponseSchema` with `envelopeViolations` = ['success is missing, must be a boolean']. It is exported anyway because the ruling names both symbols and narrowing a ruling is a report, not a dev's decision — the question is handed to contract review in the PR body and in open_questions. Clause-2 was NOT downgraded: the published surface widens either way. A SECOND premise, stated by both the card and the dispatch, IS falsified: `packages-get-version-scope.test.ts` §4 does not pin the interim `404` — its own docblock says the status was left out on purpose so the pin would survive this fix, and it would have stayed green. It was changed deliberately anyway (the PR body says so and why): §4 now pins 400 + VALIDATION_ERROR + the nested body + the message by derivation, and a new §5 pins the card's actual criterion, that the three refusals are mutually distinguishable. Both genuine 404s survive and are pinned on `error.code` as well as status. Not a breaking change, measured: the 404 being replaced came from #17668 (1a25f4a8d), which is not an ancestor of the `@objectstack/runtime@17.4.0` tag (exit 1) with two control commits from that tag's history answering exit 0 in a non-shallow checkout — it has never shipped. Coordinator docs-drift advisory folded in and re-measured on the branch, ⛔ not taken on trust: `content/docs/api/client-sdk.mdx`'s error table ALREADY gives `VALIDATION_ERROR` / 400 for 'a repeated query parameter', so this door contradicted a published page for as long as it answered 404 — a stronger justification than the landed precedent alone, and ⛔ nothing to edit there. `content/docs/kernel/contracts/metadata-service.mdx` states that a missing id on this route answers `404 RESOURCE_NOT_FOUND` with message `Package 'ID' not found`; both halves are asserted with `toBe` in §1 and the message again in the new ordering pin, and the test comments now name both pages so each pin says what it protects. ⛔ No docs file is edited and no release-owned page is touched. ⚠️ Carried forward rather than papered over: the advisory reports `packages/rest/src/index.ts` yielded NO anchor, so pages documenting the new exports are invisible to that run — its silence there is not coverage.",
      "tests": "All on the final commit 2969466d0 — the branch is 4f530786e (a merge of origin/main 43df8db3a, which touches only CI workflow/script paths, no overlap with this diff) plus one comment-only commit naming the two documentation pages. The whole gate sweep, the runtime suite and repo-wide lint were RE-RUN on 2969466d0 rather than carried over: gate families re-derived (set identical, 61), 61 run, ALL exit 0 this time including check:type-check-debt and check:dual-build-cjs-loads, reconciliation 61 derived / 61 run / 0 NOT-MEASURED / 0 UNRUN; pnpm --filter @objectstack/runtime test 260 files / 3623 passed, VERDICT command-exit 0; pnpm lint exit 0; the pin suite 13 passed. Heavy runs via scripts/pm/os-verify-lock.sh, slot issue-17672-dev, every verdict read from the printed VERDICT line. REPRO FIRST, before any edit, on branch point 310760d22, real HttpDispatcher + real SchemaRegistry, one host: repeated ?version=a&version=b -> 404 RESOURCE_NOT_FOUND; ?version=99.0.0 -> 404 RESOURCE_NOT_FOUND; unknown id -> 404 RESOURCE_NOT_FOUND; unscoped -> 200. All three refusals identical on status AND code. AFTER: repeated -> 400 VALIDATION_ERROR with body { success: false, error: { code, message, httpStatus: 400 } } and message 'The \"version\" query parameter was supplied 2 times. Supply it at most once — this endpoint will not choose between conflicting values.'; both 404s and the 200 unchanged. SUITES: pnpm --filter @objectstack/rest test -> 190 files, 3181 passed, 1 skipped, VERDICT command-exit 0. pnpm --filter @objectstack/runtime test -> 260 files, 3623 passed, VERDICT command-exit 0. pnpm --filter @objectstack/runtime test:repo -> 2 files, 69 passed, exit 0. typecheck for both packages (incl. check:test-typecheck) exit 0. Builds: runtime dependency closure exit 0; both changed packages exit 0; full workspace turbo build 72/72 successful. pnpm lint (repo-wide, eslint . --no-inline-config) exit 0 — no narrowing claimed. GATES: scripts/pm/dispatch-gates.mjs --commands derived 61 families on the merged tree with --repo asserted; all 61 run, --ran reconciliation carrying a per-family exit code reports 61 derived / 61 run / 0 NOT-MEASURED / 0 UNRUN, a DERIVED zero. Two first answered exit 3 = PREREQUISITE NOT MET (NOT a pass, NOT a finding): check:dual-build-cjs-loads needed a whole-workspace dist and went to 0 after the full build; check:type-check-debt OOM'd under a NODE_OPTIONS heap cap tighter than the one it declares for itself and went to 0 without it (5 ledger entries re-measured, 55 raw tsc errors, none above its record). Also run because the derivation flags their rosters as sitting under a changed path: check:error-status-conformance, check:error-code-casing, check:route-ledger-census, check:authz-resolver, check:published-readme-exports — all exit 0. check:api-surface is not a script at this root (exit 254, ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL) — recorded as a mis-spelled command, NOT as a measurement. PUBLISHED-SURFACE READING: built packages/rest/dist/index.d.ts gains exactly two declarations, `repeatedQueryParamMessage(name: string, count: number): string` and `refuseRepeatedQueryParams(req: any, res: any, names: readonly string[]): boolean`; SingleQueryRead, readSingleQueryValue, FILTER_SLOT_QUERY_PARAMS, assertFilterParamSuppliedOnce and repeatedFilterParamMessage are all absent from it. ABLATION (the control acceptance item 2 asks for), run from the COMMITTED state, trap 'restore' EXIT INT TERM with absolute paths: replaced the shared sentence tail in packages/rest/src/query-multiplicity.ts. On-disk proof, not the editor's exit code — grep -c removed text 1 to 0, injected text 0 to 1; git hash-object e6887a55 to 1482e095 against the HEAD blob hash e6887a55. Leg A, packages/rest's own literal pin rest-server-query-multiplicity.test.ts: 15 failed | 17 passed, exit 1. Leg B, the dispatcher door re-driven with NO rebuild: wire message became '... Supply it at most once — ABLATION-17672 the shared sentence moved.' and a temporary literal assertion on the door went red. Leg C, the committed derivation pin: still 13 passed, exit 0, which is what it is written for. Restore verified by an empty git diff HEAD for that path. ⚠️ No rebuild was needed because packages/runtime/vitest.config.ts aliases @objectstack/rest to ../rest/src/index.ts — the suite resolves the specifier to SOURCE, so scripts/ablation-dist-preflight.mjs (the dist-resolved preflight) does not apply; the ablation reaching the door with no build IS the proof that source is the resolved path. NOT MEASURED: CI's own path-scheduled jobs (Test Core shards, Temporal Conformance, Dogfood Regression, Dogfood Verify CLI, Build Core) and the 49 artifact-roster / 10 wide-population families the derivation prints outside the runnable 54-61 — reason: they are CI's runs, and the five job families have no local invocation. PLATFORM MEASUREMENT taken while delivering, recorded because it cost a repair: on PR #17815 the session-URL footer sent with the CREATE was stored byte-identical, one footer (read-back diff: only a stripped trailing newline). The first body EDIT (REST PATCH) then APPENDED the bare-footer block on top of it, leaving TWO. Repaired by re-sending the body with NO footer of my own, after which the read-back shows exactly one (the platform's bare form). ⇒ on this surface create and edit disagree, matching AGENTS.md's 'never re-send a body that already carries an appended footer'. The PR body now carries one bare footer; durable session attribution lives in the commit trailers and in this comment.",
      "mcp_calls": "4 — issue_read(get) on 17672, search_issues (one targeted dedup query, taken only after the REST search endpoint answered 403 'sessions are bound to their configured repositories'; channel switch declared), issue_write(create) for 17813, and this report comment. Card body and BOTH binding comments (5643017415, 5644242643) were read zero-quota off the /issues/17672 page payload, whose frontTimelineItems reported edges 12 = totalCount 12 with hasNextPage false. Branch pushes, draft-PR create, two body PATCHes with read-backs, the PR label write + read-back, and the tag/ancestry readings all went through git or the repo-scoped REST channel, which probed 200.",
      "open_questions": [
        {
          "question": "`refuseRepeatedQueryParams` is now published but has no cross-package consumer and cannot acquire one at a dispatcher domain — should it stay on `@objectstack/rest`'s barrel?",
          "options": [
            "A — keep both, as the ruling's route-1 wording names: the pair is coherent (a gate plus its message) and any sibling mounting `(req, res)` handlers can use it, which is precedent this barrel already sets with `prepareImportRequest` / `runImport` for plugin-auth; the export site carries a clause telling a dispatcher-domain author not to reach for it",
            "B — publish only `repeatedQueryParamMessage`: minimal widening, and it removes a footgun whose only plausible cross-package caller is exactly the author who must not use it"
          ],
          "recommendation": "A for now, and B is a one-line change on this branch that touches nothing else. Reason: the measurement falsifies an assumption embedded in the ruling's phrasing, not the ruling's direction, and `needs:contract-review` is already hung on both carriers — an at-tier review is the seat that should choose, not this one. ⛔ Clause-2 is unaffected either way and was NOT downgraded."
        },
        {
          "question": "The card and the dispatch both state that `packages-get-version-scope.test.ts` §4 pins the interim `404`. Measured, it does not — its docblock says the status was deliberately omitted so the pin would survive this fix. Does the seat want that correction recorded anywhere beyond this report and the PR body?",
          "options": [
            "A — no further action: the PR body states it and §4 was upgraded deliberately anyway",
            "B — the seat notes it on the card, since the same sentence was carried forward from the card into the triage ruling and into the dispatch without anyone re-reading the file"
          ],
          "recommendation": "A. The pin is now stronger than either description assumed, and nothing downstream depends on the old characterisation. Raised only because it is a premise that three documents repeated and none measured."
        }
      ],
      "out_of_scope_findings": [
        "filed as #17813: `metadata-protocol`'s `repeatedQueryParamError` docblock claims its wording is `packages/rest`'s 'verbatim so a caller ... is told the same thing twice', and driven, the two sentences differ at the first quoted character (The \"top\" vs The 'top') plus an appended clause; not fixable here because @objectstack/metadata-protocol does not depend on @objectstack/rest and the arrow points the other way, so it is a layering decision",
        "noted, not filed: the multiplicity check sits AFTER the id lookup, so an unknown id with a repeated ?version= still answers 404 not-found rather than 400. That is #17416's deliberate ordering and this card moved the status of a refusal, not the order of two refusals; now pinned explicitly instead of incidental. Successor: the contract review on this PR, or whoever converts the /packages lane to a closed parameter set (#17667's territory, currently needs-user-decision)",
        "noted, not filed: `readRequestedVersion` in the domain still implements the same count-not-shape predicate as `readSingleQueryValue` in packages/rest. Importing it too would mean a third exported symbol plus its result type — exactly the widening the fork instruction guards — and the door's copy also carries the `latest` sentinel, which is domain semantics. The drift surface the module's header actually names is the MESSAGE, and that is now imported. Successor: none — this is a note for the reviewer of this diff and nothing else is queued against these lines",
        "noted, not filed: the PR-body footer behaviour measured above — create stores the session-URL footer verbatim, the first edit appends a bare one on top. Already stated as a rule in AGENTS.md's 'GitHub mutates body BYTES' block and in pm-dispatch references/platform-readings.md, so this is a confirmation of a recorded reading rather than a new one. Successor: none — no card is owed for a behaviour the instruction set already describes and I followed the prescribed repair"
      ]
    }

    Generated by Claude Code

  3. claude commented on Sep 12, 2026

    @claude
    Contributor

    Landed — 2b08a7299cbd53edfaeaa9d949da71cc1c8471b1

    Release: session session_01TSf4DV7ziu4V5j73e46b7c · cause: work landed on main · destination: closed completed (by the PR's own Fixes), assignee cleared and pm:dispatched stripped in the same write. domain:cli execution seat (#6024), 2026-09-12T09:04Z.

    PR #17815 merged 2026-09-12T09:02:04Z. Acceptance verdict 5644630505; contract review of record 5644634770.

    Landing verified — two readings plus controls

    reading result
    git rev-list --parents -n 1 2b08a729 2 fields ⇒ single-parent squash
    git merge-base --is-ancestor 2b08a729 origin/main exit 0
    control same test on the pre-squash head 2969466d0c → exit 1 ⇒ the test can return false

    Reading 2, on origin/main: the barrel's export { refuseRepeatedQueryParams, repeatedQueryParamMessage } → 1 · packages/runtime imports the message → 1 · a local copy of the message in packages/runtime → 0 · the false header sentence reads no \version`→ **0** · control on that same file,one home` → 1 ⇒ the matcher fires, so both zeros are readings.

    ⇒ the rule has one implementation, the dispatcher calls it, and the sentence that was measurably false since #17668 is gone.

    ⚠️ This landing took a detour, and the detour was this seat's fault

    The PR was dequeued and re-enqueued at 08:34 because Governed Surface Guard refused the merge group: the contract-review PASS was on record, this seat stripped needs:contract-review from this card, and stopped there — while the second carrier on the pull request stayed on. The guard reads the PR's label. Full record at 5644774782; on the re-enqueued group that guard reads success.

    ⭐ Two things carried forward from it, both now in force:

    • needs:contract-review is a TWO-carrier gate. Clearing one is not clearing the gate. ⛔ Strip both, read both back, in the same act.
    • Disarming auto-merge does NOT dequeue — only convert_to_draft does (measured: removed_from_merge_queue followed the draft conversion, not the disarm).
    • ⭐ And read the job LOG, never the check NAME: that guard's first leg was CLEAR («the diff touches no governed surface»); the refusal came from its second, unrelated leg. This seat holds a standing rule that a particular aggregator red is never real — applying it by name here would have dismissed a genuine violation.

    What the card delivered

    Route 1 as ruled: the one shared rule is reachable, the door answers 400 VALIDATION_ERROR from it, and the owed header correction rode along on the same branch. ⛔ No copy of the rule in packages/runtime — the one thing no pressure made acceptable — and the file now carries explicit ⛔ comments at :80 and :601 saying the res-shaped gate is not what a dispatcher domain uses.

    ⭐ Premise ② bent and the fork correctly did not fire. Only one of the two ruled symbols is callable at this door (refuseRepeatedQueryParams writes onto a res; a dispatcher domain returns { handled, response } and its body fails BaseResponseSchema). The fork's trigger is more than two symbols needed; here fewer are usable. The dev exported both anyway because the ruling names both, and narrowing a ruling is a report rather than a dev's decision — and handed the question up. The contract review ruled A on jurisdictional grounds, ⛔ not aesthetic ones; B (publish only the message) stays available at one line.

    ⚠️ A second premise three documents repeated and none measured, including this seat's dispatch order: packages-get-version-scope.test.ts §4 was said to pin the interim 404. It does not — its own docblock says the status was deliberately omitted so the pin would survive this fix. The dev read the file instead of the three documents and upgraded §4 deliberately anyway.

    Not a breaking change, measured: the 404 being replaced came from #17668, which is not an ancestor of the @objectstack/runtime@17.4.0 tag (exit 1, two control commits from that tag's history at exit 0, non-shallow) ⇒ it never shipped.

    Filed by the dev out of this round rather than folded in: #17813 (metadata-protocol's repeatedQueryParamError docblock claims verbatim parity with packages/rest and the two sentences differ — a layering decision, since the dependency arrow points the other way).


    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

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions