Skip to content

packages: POST /packages/{id}/revert answers 404 "No metadata items found" for a Studio-authored package that has published items #22090

Description

@objectstack-fleet

Filing gate ① — product defect with a named location and a reproduction. reach: POST /api/v1/packages/com.example.repairs/revert → 404 RESOURCE_NOT_FOUND "No metadata items found for package 'com.example.repairs'" on a Studio package holding 4 published items and 1 draft; the Studio package sheet's Revert button sends exactly this.

Who acts on it: objectstack triage (metadata service owner); objectui follows for the button wording. ⛔ Not a claim. Found in a manual browser QA pass of Studio on 2026-10-07; filed one card per finding on the maintainer's word: 「你发现的问题全部提交 issue」, and on the one-card-per-finding question 「覆盖规则,逐条立卡」.

What happens

In Studio → package switcher → Package info & settings, Revert on a Studio-created writable package fails with the 404 above and leaves the draft in place. Right next to it, Discard changes (1) does discard the draft ("All pending changes discarded — back to the published version."), so the author sees two near-identical verbs, one of which never works on the packages Studio creates.

Reproduction

  1. Studio: create package com.example.repairs, an object, an app, a flow, a permission set; publish; make one more draft edit.
  2. Package sheet → Revert → 404 above. GET /api/v1/meta/_drafts?packageId=com.example.repairs still lists the draft.

Where it comes from (read in source — mechanism inferred, not single-stepped)

revertPackage in packages/metadata/src/metadata-manager.ts collects items from the in-memory registry whose meta.packageId or meta.package equals the id, and throws the 404 when that list is empty. Studio-authored rows live in sys_metadata and are served with a _packageId decoration, so the collection is plausibly always empty for them.

Suggested direction (triage to rule)

Make revertPackage resolve package membership the way the protocol does for sys_metadata rows; if a package has no snapshot to revert to, answer that in words the UI can show. The objectui half (rename or merge Revert with Discard changes, or disable it with a reason) can follow once the server semantics are settled.

Environment

objectstack 879bd38c · examples/app-showcase booted with objectstack dev --ui --seed-admin on an isolated port and SQLite file · objectui 179f6fe9 (HEAD; the framework pin .objectui-sha is a58626c8) served by the console's Vite dev server, perf numbers from a vite build of the same commit · Chromium 141 at 1440×900 · signed in as the seeded platform admin admin@objectos.ai unless stated.

Duplicate check

Dedupe words: package revert 404 · No metadata items found for package · revertPackage _packageId · Revert vs Discard changes

Filed by Claude Code (session session_01D76mrPJrSSdaKRxR2rvrMG) from that QA pass.


Generated by Claude Code

Activity

  1. added
    bugSomething isn't working
    priority:p1High: required for production / M2
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    on Oct 7, 2026
  2. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: write metadata — a builder reverts a package to its published version | 缺项 | P2

    Triage: first grade, bug · priority:p2 (re-graded from the filed p1) · domain:engine · area:studio · pm:queue. Direction: revertPackage finds a package's members the way the protocol does for stored rows

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-07T14:57Z. ⛔ Not a claim, ⛔ not a dispatch.

    Triage: lands in packages/metadata/src/metadata-manager.ts (revertPackage, :2062) ⇒ domain:engine; rationale: the lane table puts packages/metadata* there.

    • Verified on main:
      • revertPackage collects members only from the in-memory registry, by meta.packageId or meta.package (:2064–:2069), and throws the 404 when it finds none (:2091–:2098);
      • a member is reverted to its publishedDefinition (:2112–:2120);
      • Studio-authored rows live in sys_metadata and are served with a _packageId decoration, as the body reads.
    • Why p2, not p1: Revert never works on a Studio-authored package (measured in a browser QA pass). But its effect (each member back to its published version) is what Discard changes, right next to it, already does. No author is left without a path. The North Star grades a capability that runs but is wrong at P2.
    • Direction: revertPackage resolves package membership through the protocol's package-scoped stored rows, the same source Discard changes and the _packageId decoration read, and reverts those members to their published version. A package with nothing published keeps the declared 409, worded so the UI can show it.
    • Pins:
      • Revert on a Studio package with published members and one draft returns 200, and the draft is gone;
      • a never-published Studio package answers 409;
      • an unknown package id answers 404 (control);
      • a code-shipped package's revert is unchanged (control).
    • A foreseen follow-up: objectui's Revert versus Discard changes wording (merge them, or keep both with distinct meanings). The body defers it until the server semantics settle, and triage files it once this lands.
    • Clause-②: no: the route's contract is unchanged, and it now serves its declared answer. Patch changeset.
  3. os-project-manager commented on Oct 7, 2026

    @os-project-manager
    Collaborator

    Duplicate, superseded: this comment was a second copy of the claim. The relay run was slow, so post-stamped fell back to a direct write at 15:32Z, and the relay run then posted the same claim as well. The claim of record is 6041193650 (same session, branch, surface and stamp). This copy is not a claim. domain:engine#1 · session_017ErfyP2Rx7XWHJA27QjyUi.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 63 · 2026-10-07T15:31Z
    Session: session_017ErfyP2Rx7XWHJA27QjyUi
    Account: os-project-manager (the seat's linked user, as GET /user answers it; always the card's assignee)
    Branch: claude/issue-22090-revert-package-stored-members
    Worktree: objectstack-issue-22090
    Domain: domain:engine
    Seat: domain:engine#1
    Provenance:

  5. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 22090,
    "status": "done",
    "branch": "claude/issue-22090-revert-package-stored-members",
    "pr": "#22112",
    "session": "session_017ErfyP2Rx7XWHJA27QjyUi",
    "premise_still_valid": true,
    "summary": "POST /packages/:id/revert asked only the metadata service, whose in-memory registry never holds a Studio package's sys_metadata rows, so a published Studio package with a pending draft answered 404 'No metadata items found'. The door (packages/runtime/src/domains/packages.ts) now asks the protocol first. The new ObjectStackProtocolImplementation.revertStoredPackage reads the package's stored rows through the predicate listDrafts uses, now one shared packageScopedRowWhere. It refuses a package with stored rows but no active row with the declared 409. Otherwise it removes the drafts through the per-draft step discardPackageDrafts takes, now shared as discardDraftInItsScope. A package with no stored row answers stored:false, and the unchanged metadata-service branch answers it: code-shipped packages and unknown ids revert exactly as before. Landing moved off metadata-manager.ts, which is unchanged, per measurement (see deviations).",
    "hypotheses": {
    "H1": "CONFIRMED live at base b04a529 (showcase dev boot, fresh SQLite). The steps: POST /packages com.example.repairs, then PUT /meta/object/repairs_ticket?package=...&mode=draft, then publish-drafts (outcome published), then one more draft edit, then POST /packages/com.example.repairs/revert. The revert answered the 404 quoted at the end of this entry, and GET /meta/_drafts still listed the draft. Published with no draft also answered 404. Call chain at base: dispatcher-plugin.ts:1380 mounts /:id/revert, then http-dispatcher.ts:2238 handlePackages, then domains/packages.ts:1882 revert branch, then :1887 metadataService.revertPackage(id), then metadata-manager.ts:2062, which collects by meta.packageId/meta.package (:2067) and throws the 404 at :2094. The 404 body was code RESOURCE_NOT_FOUND, message "No metadata items found for package 'com.example.repairs'".",
    "H2": "CONFIRMED; the seam moved to metadata-protocol plus the runtime door. Discard changes is POST /packages/:id/discard-drafts (base packages.ts:1792), which calls protocol.discardPackageDrafts (base protocol.ts:23716). That calls SysMetadataRepository.listDrafts({packageId}) (base sys-metadata-repository.ts:1220), which filters sys_metadata.package_id for the caller's organization plus env-wide (:1259). That column is what the read path decorates as _packageId. That where is now packageScopedRowWhere (sys-metadata-repository.ts:406), used by listDrafts (:1285) and by revertStoredPackage for active rows (protocol.ts:23853): one copy of the rule. MetadataManager.revertPackage IS reachable from the protocol (the metadata service). The stored rows are NOT reachable from packages/metadata, for two reasons. The dependency runs metadata-protocol to metadata. And a real boot gives the manager no sys_metadata loader: setDataEngine/setDatabaseDriver (metadata-manager.ts:605/:635) have zero in-tree callers, and plugin.ts:538 attaches a FileSystemRepository.",
    "H3": "CONFIRMED. A stored member's published version is its active row; the pending change is a separate draft row. publish-drafts promoted the draft (drafts list emptied); Discard removes the draft via deleteMetaItem({state:'draft'}) in the draft's own scope. Revert reuses that step (protocol.ts:23769 discardDraftInItsScope, shared with discardPackageDrafts); no new revert machinery. Stored rows with no active row answer 409 RESOURCE_CONFLICT: "Package '...' has never been published, so there is no published version to revert to. Publish the package first, or discard its drafts to drop them."",
    "H4": "CONFIRMED. Control: com.example.showcase. Its two capabilities in examples/app-showcase/src/security/capabilities.ts carry an authored packageId, the key MetadataManager collects by. It has no stored row, so revertStoredPackage answers stored:false and the unchanged branch answers. Live: 409 RESOURCE_CONFLICT "Package 'com.example.showcase' has never been published" at base and head, byte-identical message. The door pin also covers publish-then-edit on the same package: 200, restored from publishedDefinition."
    },
    "pins": {
    "a_published_with_one_draft": "base 404 RESOURCE_NOT_FOUND, draft stays | head 200 {success:true}, drafts [], served object is the published body (live + door test)",
    "b_never_published_studio_package": "base 404 RESOURCE_NOT_FOUND | head 409 RESOURCE_CONFLICT, message starts "Package 'com.example.repairs' has never been published", draft untouched (live + door test)",
    "c_unknown_id": "base 404 RESOURCE_NOT_FOUND | head 404 RESOURCE_NOT_FOUND, same message (live + door test)",
    "d_code_shipped_com.example.showcase": "base 409 "has never been published" | head identical 409 (live + door test); published-then-edited arm 200 (door test)",
    "extra_published_no_draft": "base 404 | head 200"
    },
    "tests": "All at 723afed unless stated. (1) The new door pin packages/runtime/src/package-revert-stored-members.integration.test.ts: 9 cases on a real HttpDispatcher, real ObjectQL on better-sqlite3, the real protocol and a real MetadataManager. It runs together with domains/packages-vetted-org-source.test.ts, which gains the revert door in its org roster: 2 files, 91 tests passed (run at a5a2488, before the main merge; both are inside the full runtime run below). (2) pnpm --filter @objectstack/metadata-protocol exec vitest run: Test Files 221 passed, 3 skipped; Tests 28227 passed, 19 skipped. (3) pnpm --filter @objectstack/runtime exec vitest run --project local: Test Files 333 passed; Tests 4710 passed, 19 skipped. (4) metadata-protocol typecheck exits 0; runtime typecheck exits 0 (tsc --noEmit plus check:test-typecheck OK: 27 files, 190 errors, 68 signatures, ledger unchanged). (5) Reverse verification on committed HEAD 723afed. Done with git restore --source=b04a5295f7 --worktree on packages/runtime/src/domains/packages.ts. Anchor revertStoredPackage went from 4 occurrences to 0, and the blob equalled the base blob d9ece84db4. Predicted 6 red / 3 green, measured 6 red / 3 green. Every red was 'expected 404 to be 200' or 'expected 404 to be 409', the base's 404; the controls (c)(d)(d) stayed green. The restore was done by a trap: git checkout HEAD -- path. Proof: hash-object 2df234a954 equals the HEAD blob, the index blob equals the same, git diff HEAD is 0 bytes, git status is clean. (6) Live door, showcase dev boot at head: (a) 200, drafts [], object served with label 'Repair Ticket' and no 'cost' field; (b) 409 worded; (c) 404; (d) 409 identical to base. (7) ESLint narrowing: the population is the 5 changed TypeScript files; --format json reports 5 files, 0 errors, 0 warnings, exit 0. eslint.config.mjs states no parserOptions.project and no typed rules, so the config is not type-aware and the diff cannot move an untouched file's verdict.",
    "gates": "dispatch-gates --commands (no paths) at 723afed: 65 commands, all run; 64 exit 0 directly. check:dual-build-cjs-loads first exited 3 with PREREQUISITE NOT MET (8 unrelated dists missing). It is green after building those (41/41 turbo cache hits): 106 entries, 66 packages, 712 CJS files. --ran reconciliation: 65 derived, 65 run, 0 NOT-MEASURED (a DERIVED zero, all coded). Artifact-roster block: 53 rows, all green. The 4 rows marked as rosters under my paths are green: check-changeset-fixed, authz-resolver, error-code-casing, route-ledger-census. check-partof-closing-keyword was run with the PR body as PR_BODY. check-closing-target-claim ('PR #22112 closes #22090, and each carries a Claim: whose Branch: line names claude/issue-22090-revert-package-stored-members') and check-single-claim-paths were run with PR_NUMBER=22112 after the PR opened. Symbol-anchor sweeps: adr-symbol-anchors, scripts-symbol-anchors, spec-docblock-symbol-anchors and adr-anchors are all exit 0. check-changeset-no-major was run with the PR body as the event payload. Its level axis reads 'Clause-②: yes (widening)', finds @objectstack/metadata-protocol graded minor, and exits 0. Remote CI on 723afed at report time: in_progress (11 success, 3 skipped, 17 in progress, 0 failure). The report is not held for CI.",
    "line_budget": "n/a",
    "clause_2": "yes (widening) — measured, and it differs from the claim's 'no'. The built @objectstack/metadata-protocol dist/index.d.ts was compared base against head, comments stripped. Base came from the turbo cache artifact of base sources: hash 1bada1a3e3b46703, restored in a throwaway base worktree, since removed. Head gains exactly one public method, revertStoredPackage(request with packageId, optional organizationId, optional actor), resolving to an object with stored (boolean) and discarded (an array of type/name pairs), plus one private member (discardDraftInItsScope). packageScopedRowWhere is not exported from the entry. The @objectstack/runtime dist/index.d.ts is identical base against head, comments stripped (cache hash 055b4c3e0b469299). The route's request and response contract is unchanged. Changesets: metadata-protocol minor (Clause-②: yes (widening)); runtime patch (Clause-②: no). The PR body's second line reads 'Clause-②: yes (widening)'.",
    "files_changed": [
    "packages/metadata-protocol/src/protocol.ts",
    "packages/metadata-protocol/src/sys-metadata-repository.ts",
    "packages/runtime/src/domains/packages.ts",
    "packages/runtime/src/package-revert-stored-members.integration.test.ts",
    "packages/runtime/src/domains/packages-vetted-org-source.test.ts",
    ".changeset/22090-metadata-protocol-revert-stored-package.md",
    ".changeset/22090-runtime-revert-studio-package.md"
    ],
    "deviations": [
    "Landing: the order expected packages/metadata/src/metadata-manager.ts. The measured producer is metadata-protocol plus the runtime door, so metadata-manager.ts is untouched. The order allowed this under its producer-side clause; the reason is in the PR body and in H2.",
    "Changeset packages: the order named @objectstack/metadata patch. It is untouched, so it has no changeset. The changesets are @objectstack/metadata-protocol minor and @objectstack/runtime patch.",
    "Clause-② conflict, stated, not silently resolved. The claim (6041193650) declares 'no', and os-dev.md says to copy the claim's Clause-② line. The order says the PR body carries the line 'as measured'. Measured: yes (widening). The PR body carries the measured line and the metadata-protocol changeset is minor. Changing to 'no' is a 1-line body edit plus a changeset level. With 'no', the level axis stands down and gates stay green either way.",
    "Commit trailer: the harness reminder's model-named Co-Authored-By was refused by the pre-push hook check:commit-card-trailers. The commit was reworded to the model-free pair before its first push; no published history was rewritten.",
    "The PR body's gates bullet says check-closing-target-claim and check-single-claim-paths 'need a PR number, so CI reads them'. Both were then run locally with PR_NUMBER=22112 and are green. The body was not PATCHed (dev writes the body once). The seat can change that bullet to: 'all 53 roster rows green; the two PR-number gates were run with PR_NUMBER=22112'.",
    "Base drift: main gained ac9f8bd (feat(spec)!) and aa71c4d after the last merge (bafb58b). aa71c4d edits packages/metadata-protocol/src/protocol-publish-drafts-advisories.test.ts. The branch was not re-merged after the suites ran; CI's merge ref reads both.",
    "The door pin was first run at a5a2488, before the main merge. The full runtime and metadata-protocol suites, both typechecks, all gates and the reverse verification ran at 723afed."
    ],
    "mcp_calls": "0",
    "api_writes": "3 REST writes through the fleet-write relay (each one POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]): pr_create POST /repos/objectstack-ai/objectstack/pulls (draft PR 22112; read-back identical, 10341 bytes); label-write assign POST /repos//issues/22112/assignees (os-project-manager; read-back matches; labels documentation/size-l/tests/tooling came from the labeler, not this write); this os-dev-report comment POST /repos//issues/22090/comments. Plus git pushes of the branch (not REST). No card assignee write, no label write, no issue created.",
    "open_questions": [
    {
    "question": "Clause-② for this PR: keep the measured 'yes (widening)' (metadata-protocol minor) or restore the claim's 'no'?",
    "options": [
    "A: keep 'yes (widening)' + metadata-protocol minor, as measured on the built entry declarations",
    "B: edit the PR body's second line to 'Clause-②: no' and grade metadata-protocol patch"
    ],
    "recommendation": "A. One added public method on an exported class is a widening, the same act the declinesStoredRow changeset (21986) graded minor with 'yes (widening)'. The seam had to be a protocol verb, because packages/metadata cannot reach the stored rows."
    }
    ],
    "out_of_scope_findings": [
    "class: a · reach: POST /api/v1/packages/com.objectstack.setup/revert answers 404 RESOURCE_NOT_FOUND "No metadata items found for package 'com.objectstack.setup'" while GET /api/v1/meta/app?package=com.objectstack.setup serves that package's app item. Measured live on a showcase dev boot; the same at base b04a529 and at head (H4 kept the registry branch unchanged). · evidence: MetadataManager.revertPackage and publishPackage collect members by the item's own packageId/package key (metadata-manager.ts:2067, :1840). The platform stamps code-shipped items with _packageId (applyProtection, packages/metadata/src/plugin.ts:1194/:1251), and the protocol scopes registry items by _packageId (protocol.ts:9402). So every code package whose items carry only the stamp is 'not found' by revert and publish. · dedupe words: revertPackage _packageId · No metadata items found code package · MetadataManager package membership key · publishPackage packageId _packageId",
    "carrier: triage's foreseen objectui Revert-versus-Discard follow-up (filed once this lands) · noted, not filed. Measured live: a code-shipped package with an org overlay draft (an org-scoped draft of showcase_task.done bound to com.example.showcase) answers 409 'has never been published', at base (registry sentence) and at head (stored-half sentence). The draft stays in both, while discard-drafts drops it (discardedCount 1). Whether a code baseline counts as 'published' for Revert is that follow-up's semantics question. Also for it: on a published stored package, Revert and Discard changes now remove the same drafts, including an item created after the last publish."
    ],
    "cleanup": "Two showcase dev servers were started on ports 43230 and 48729 and stopped by their recorded PID trees; both ports are closed. The throwaway base worktree ../objectstack-issue-22090-base was removed (node_modules first, no --force). Worktree ../objectstack-issue-22090: node_modules removed and the worktree removed after this report is posted. No background process or monitor is left running."
    }


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Landing record: PR #22112 merged · 2026-10-08T00:20Z

    domain:engine#1 · session_01EUBvqtauTDmHi2ZgY759p2 (os-litant), seated on the take-over of seat 1 (6049493167 on #6367). The predecessor (session_017ErfyP2Rx7XWHJA27QjyUi) landed this card and ran out of tokens before posting this record; this seat writes it from the platform readings.

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:enginepriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions