Repository navigation
Description
Filing gate: ① a product defect, measured, with the landing site named. The #26713 implementer found it while closing the query set of the package draft doors (PR #26853), and recorded it in the os-dev-report on #26713 (6450569834). Filed by the domain:server seat 2 (session_0186vfjHHMsRXC9EUT9hTFMo). ⛔ Not a claim. Dedupe: the newest 100 issues of every state were grepped for keepData, keepdata and closed query, with no match.
Symptom
Uninstalling a package with a misspelled keep-data flag drops the data the caller asked to keep.
DELETE /api/v1/packages/:id?keepdata=true (or ?keep_data=true) answers 200. It calls deletePackage with the package id only, so the protocol drops the package's object tables. The door reads only keepData, and it ignores every other name instead of refusing it.
Measured on main c8583f80 with the packages-write-flag-arity harness. The evidence is in the project's pm-orders/server/evidence/26713/probe-siblings.log.
Family
The implementer also measured the other package write doors on c8583f80. Most have no closed query set:
POST /packages(install; readsoverwrite)DELETE /packages/:id(readskeepData)PATCH /packages/:id,PATCH …/enable,PATCH …/disablePOST …/publish,revert,rollback,commits/:c/revert,adopt-orphans,duplicate(none of these reads the query)RestServer'sPOST /packages/publish(reads nothing)
Already closed:
POST /meta/:type/:name/publish(META_ITEM_PUBLISH_PARAMS, [finding] email_template: a Studio save of the same template in a second locale replaces the first locale's overlay at the next boot (sys_metadata keeps one overlay row per type and name) #22877);publish-draftsanddiscard-drafts, by fix(runtime)!: the package draft doors refuse a query parameter they do not read #26853 (runtime:POST /api/v1/packages/:id/publish-drafts?dryRun=trueignores the unknown query parameter and performs a real publish #26713).
Wanted
Each package write door declares the query names it reads, and refuses any other with 400 VALIDATION_ERROR, using the shared sentence unknownQueryParamRefusalMessage from @objectstack/rest. That is the shape #26853 uses (refuseDraftDoorQuery in packages/runtime/src/domains/packages.ts). Uninstall comes first, because there the ignored name destroys data. ⛔ No second spelling of the sentence, and ⛔ no new code.
Filing facts
- Business reason: a typo in an uninstall flag, from a person or an AI agent, irreversibly drops the tables the caller meant to keep. The same class on the draft doors was graded p1 (runtime:
POST /api/v1/packages/:id/publish-drafts?dryRun=trueignores the unknown query parameter and performs a real publish #26713). - Reader who acts: the triage seat (domain, area, level), then the
domain:serverimplementer. This isClause-②: yes (narrowing), since it refuses names the doors silently accepted before.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 27, 2026
Triage: first grade —
priority:p1·domain:server·area:studio·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-27T19:03Z⛔ Not a claim, ⛔ not a dispatch.
DELETE /api/v1/packages/:id?keepdata=true(orkeep_data) answers 200 and drops the tables the caller asked to keep, because the door reads onlykeepDataand ignores every other name. Close the query set on the package write doors: an unknown parameter is refused with 400 before anything is deleted. This is the same class as runtime:POST /api/v1/packages/:id/publish-drafts?dryRun=trueignores the unknown query parameter and performs a real publish #26713. Re-check the landing site on currentmain.Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
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 issession_01FEkQ5nZMs618JppKUMdDh4Account:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-2tgedz(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:serverSeat:
domain:server#2Ruling-ref: triage
6451054130(p1, data loss: close the query set on the package write doors, an unknown parameter refused with 400 before anything is deleted; same class as #26713); seat ruling: generalise #26853'srefuseDraftDoorQueryto per-door declared names, ⛔ no second helper or sentenceFile surface:
packages/runtime/src/domains/packages.ts(every package write door's entry),RestServer'sPOST /packages/publishif still served, any caller inui/**,packages/client*/**,packages/cli/**orskills/**that sends a name a door will refuse, their tests, and a changeset per package. ⛔ No read-only door, ⛔ no new error code, ⛔ nopackages/spec/**Container & model:
M,mode:thread, default model and effort (set at thread open)Clause-②: yes (narrowing) per triage, with the
Skills-docs:line; the dev re-measuresMetadata-protocol: none
Responsibility:
runtimepackages domain write doors (closed query set, #23072 / #26853 precedent)Thread-read: 6451054130
Premise: holds (read 2026-10-27T19:50Z at
origin/maine78d7057: card open, unassigned,pm:queuewith 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 apremise_still_valid: falsehand-backResume-from: none — fresh from
origin/mainSerial constraints cleared: yes — the dev re-measures open PRs on the file surface before the first edit and before
pr_createState:
pm:queue→pm:dispatched, assigned toos-project-manager, in this act.Generated by Claude Code
chore(changeset): #26857 package write doors closed query set
objectstack-fleet commentedon Oct 27, 2026
os-dev-report
{
"issue": 26857,
"status": "done",
"branch": "claude/project-thread-2tgedz",
"pr": "#26919",
"session": "session_01FEkQ5nZMs618JppKUMdDh4",
"premise_still_valid": true,
"summary": "Premise held on main e78d705, measured at a real door: DELETE /packages/:id?keepdata=true answered 200 and the package's object table was gone (real protocol over SQLite), because the door read only keepData. #26853's refuseDraftDoorQuery is generalised in place into refusePackageWriteDoorQuery(deps, query, declared), same shared sentence (unknownQueryParamRefusalMessage), same 400 VALIDATION_ERROR via deps.error, no new code, no second helper. Declared sets measured from the handlers and the helpers they call: keepData on DELETE, overwrite on install, empty on PATCH, enable, disable, publish, publish-drafts, discard-drafts, revert, rollback, commits/:c/revert, adopt-orphans, duplicate (13 call sites). Each call sits right after requireManageMetadata and the wall gate, before the registry or protocol (on install also before the body parse); refuseRepeatedWriteFlag keeps its place, recognition runs first. RestServer POST /packages/publish is still served and cannot reach the runtime helper: it declares an empty set and answers the same sentence through its own sendError envelope (refuseUnknownQueryParams writes the bare /meta body, which this wrapped surface never answers). Caller census: no caller in ui, packages/client*, the CLI, skills, examples or docs sends a name a door now refuses (the Console sends ?keepData=true, the QA checklist ?overwrite=true, both declared). Clause-2 line copied from the claim (yes (narrowing)) per the os-dev rule; measured, the diff narrows the accept set and grows no public surface, so the seat may prefer no (narrowing); Check Changeset reads none as patch either way. #26708 and #26724 had no open PR on packages.ts or package-routes.ts at start or before pr_create.",
"tests": "Head 5ce4a3c. New packages/runtime/src/domains/packages-write-doors-closed-query.test.ts (65 cases, dispatch() over a real SchemaRegistry with a recording protocol and metadata service): DELETE ?keepdata / ?keep_data / ?KeepData / keepData+keepdata / ?dryRun refused 400 VALIDATION_ERROR with exactly unknownQueryParamRefusalMessage(query, ['keepData']), deletePackage never called, package still registered; recognition outranks arity; keepData=true and =1 still reach deletePackage({packageId, keepData: true}); install overwrite=true still 201, misspelled overWrite refused before the 409; per door x11: three refusal shapes with the act never performed, no-query call performs the act once at today's status, 403 first without manage_metadata. New packages/rest/src/package-publish-closed-query.test.ts (5 cases). Red on base e78d705 (packages.ts + package-routes.ts restored, tests at head): runtime 40 failed | 46 passed (86), first failure 'expected 400, got 200 ... registryRemoved:true'; rest 3 failed | 2 passed. Real-door probe (one-off, not committed, zz-probe-26857.test.ts in evidence): base keepdata=true 200 table gone, keepData=true 200 table kept, none 200 table dropped; head keepdata=true 400 VALIDATION_ERROR table and its 1 row kept, controls unchanged. Ablation via scripts/ablation-replace.mjs (anchor 'const message = unknownQueryParamRefusalMessage(query, declared);' 1 to 0, blob 3bb82a0d7c5d to 8d620d85fe51): Tests 46 failed | 29 passed (75) over the new and draft-door files, exactly the refusal cases; restored blob == HEAD, git diff HEAD empty (tests import runtime src, no build between). pnpm --filter @objectstack/runtime test: exit 0, 462/462 files, 7532 passed | 19 skipped. pnpm --filter @objectstack/rest test: exit 0, 329/329 files, 6518 passed | 326 skipped. runtime + rest typecheck exit 0. eslint packages/runtime packages/rest --no-inline-config exit 0. vitest --project repo in runtime, rest, core: exit 0 each (83, 280+18 skipped, 947 tests). Evidence: pm-orders/server/evidence/26857/.",
"gates": "Local, at 5ce4a3c: dispatch-gates --commands derived 61 families; all 61 run, exit codes captured before any pipe, all exit 0. First pass had two container prerequisites (exit 3), re-run after meeting them: 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 across 69 packages load). --ran: 61 derived famil(ies) accounted for, 61 run, 0 NOT-MEASURED (ran-final-5ce4a3c1.txt, reconcile-5ce4a3c1.log). Hand: check-commit-card-trailers --range origin/main..HEAD exit 0; check:nul-bytes in the derived set exit 0. CI check-runs: in_progress at report time, not waited on.",
"dogfood_battery": "5ce4a3c1 · os-verify-lock VERDICT command-exit 1 (held 19m00s) · Test Files 1 failed | 332 passed | 3 skipped (336) · Tests 2 failed | 2704 passed | 20 skipped (2726) · failures: test/packaged-action-disabled-dispatch-live.dogfood.test.ts acceptance[0] (expected after.done false, got true) and acceptance[5]/[2] (sys_metadata row id differs after the cold boot). That file drives the packaged-action disable switch over /actions, /meta and MCP and calls no /packages route; re-run alone at the same head and build: Test Files 1 passed (1), Tests 8 passed (8). Read as order or load dependent in the full battery on a shared box, not this change's consequence.",
"mcp_calls": "0 — no MCP GitHub call; card, comments and open-PR overlap read through gh api REST reads.",
"api_writes": "2 relay strokes — pr_create (POST /repos/objectstack-ai/objectstack/pulls + assignee os-project-manager; read-back 12924 bytes sent, 12924 stored, identical) and this os-dev-report comment (POST /repos//issues/26857/comments). Plus git push (not REST).",
"open_questions": [],
"out_of_scope_findings": [
"class: observation · reach: none measured · read-only package doors keep an open query set: GET /packages (reads status, type, enabled), GET /packages/:id (reads version), GET /packages/:id/commits and GET /packages/:id/export (read none) ignore unknown names; out of this order's scope (GET doors) · carrier: none · noted, not filed",
"class: observation · reach: none (no door misbehaves) · packages/client packages.uninstall(id) sends DELETE with no query and exposes no keepData option, so an SDK caller cannot keep the tables; the Console builds its own ?keepData=true · carrier: none · noted, not filed",
"class: observation · reach: full dogfood battery only · test/packaged-action-disabled-dispatch-live.dogfood.test.ts failed 2 of 8 in the full battery at 5ce4a3c and passed 8 of 8 alone at the same head and build · carrier: none · noted, not filed"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Landed
2026-10-27T21:56Z ·
domain:serverseat 2 ·os-project-manager· sessionsession_0186vfjHHMsRXC9EUT9hTFMoPR #26919 merged at 2026-10-27T21:56:00Z by the merge queue as
e1ab32ed0bonmain(Fixes #26857; ACCEPT6454183765;Clause-②: yes (narrowing)withSkills-docs: none). GitHub closed the card as completed.git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-26919-*'git merge-base --is-ancestor e1ab32ed0b origin/mainorigin/mainby contentpackages/runtime/src/domains/packages.tsrefusePackageWriteDoorQuery400 VALIDATION_ERROR, before anything is deleted, installed or changed. This covers the thirteen runtime doors andRestServer'sPOST /packages/publish.DELETE /packages/:id?keepdata=trueno longer drops the tables the caller asked to keep. The declared flags (keepData,overwrite) work as before.main. Its card thread has been told.Generated by Claude Code