Skip to content

packages: DELETE /api/v1/packages/:id deletes unattributed (no actor), and the in-process deletePackage leaves the per-environment disable record behind #31023

Closed

Description

@objectstack-fleet

repo:cloud#3 · hotlong · session_01Q4EjjRhUZjdy8VXs6Kkur2

Filing gate: (b) a defect with a named consumer, measured by the objectstack-ai/cloud#3656 dev (PR objectstack-ai/cloud#3686, report on that card). Platform defects go to the platform: 「平台的问题应该在平台处理」 (objectstack#30765).

维护者速读

在 Studio 里删除一个应用时,审计日志记不下是谁删的:删除接口没有把操作人传下去,而发布、回滚等其他包操作都传了。另外,从平台内部调用删除(cloud 的 AI 工具、本地安装、导入都走这条路),不会清掉「此包在本环境已停用」的记录,以后重装同一个包时可能一装上就是停用状态。

Measured (pin c50e0dfa, code read)

  • runtime/src/domains/packages.ts ~3068: the DELETE arm calls protocol.deletePackage({ packageId, keepData }) with no actor. resolvePackageWriteActor (packages.ts:408, ADR-0146 D1) is used by publish-drafts, discard-drafts, revert, rollback, adopt-orphans and duplicate. The sys_packages delete (package service delete(id)) carries no actor either.
  • protocol.deletePackage does not clear the per-environment disable record (setPackageDisabled); only the HTTP door clears it.

Expected

  • The DELETE door resolves the actor as its siblings do, and both the metadata deletes and the sys_packages delete are attributed in sys_audit_log.
  • Clearing the disable record moves into the uninstall verb, so every caller gets it.

Acceptance

  • Tests: a Studio delete writes audit rows naming the person; an in-process delete followed by a reinstall leaves the package enabled.

Consumer: objectstack-ai/cloud#3656 (its tool passes an actor and calls the in-process verb).

Suggested-grade: p2.

Dedupe (2026-11-06): package delete actor audit → 363 hits, the top 3 QA runs; deletePackage disable → 22, the top 3 closed (#28408, #27395, #26857 are other uninstall defects). None covers this.

Activity

  1. added
    bugSomething isn't working
    pm:queueReady for the PM dispatch loop
    repo:cloudCard routed whole to the cloud PM: needs cloud code, a producer or a fact (cloud owns it to closure)
    domain:serverDomain lane — seat card indexed by label:pm:seat
    on Nov 6, 2026
  2. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: audit · p2 kept · domain:server · 2026-11-06T18:57Z

    ⛔ Not a claim.


    Generated by Claude Code

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

    @objectstack-fleet
    ContributorAuthor

    domain:server#4 · marchtian · session_017ihL2dLQEXWGsE2SZ3Qn5F

    Claim: PM loop round 1
    Session: session_017ihL2dLQEXWGsE2SZ3Qn5F
    Account: marchtian
    Branch: claude/issue-31023-package-delete-actor
    Worktree: objectstack-issue-31023
    Domain: domain:server
    Seat: domain:server#4
    File surface: packages/runtime/src/domains/packages.ts (the DELETE /packages/:id arm only), packages/metadata-protocol/src/protocol-package-uninstall.ts (the uninstall verb), packages/runtime/src/package-state-store.ts only if the disable-record clear moves through it, the package service's delete call site that writes sys_packages, their tests, .changeset/ (stop on breach; explain in the report)
    Container & model: M, mode:subagent (in-seat background subagent, the project's standing dev form), model: default (dispatch-gates --tier: no path-derived mandate; PM judgment, default tier), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: objectstack (packages/runtime DELETE door + packages/metadata-protocol uninstall verb; platform card per cross-repo rule 1) | no platform path covers it today: sibling doors attribute via resolvePackageWriteActor, this one does not; only the HTTP door clears the disable record | reached by every Studio delete and every in-process deletePackage caller (cloud AI tool, install-local, import — cloud#3656)
    Thread-read: 6692904021
    Premise: holds
    Serial constraints cleared: #31067 / PR #31157 (seat 4) touches packages.ts only at the two exported function declarations and index.ts — region disjoint from the DELETE arm; #30999 (uninstall dependent-rule refusal, same door) is held behind this card; no other open PR touches these files (open-PR file scan, 17 PRs)

  5. removed
    repo:cloudCard routed whole to the cloud PM: needs cloud code, a producer or a fact (cloud owns it to closure)
    on Nov 7, 2026
  6. objectstack-fleet commented on Nov 7, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#4 · marchtian · session_017ihL2dLQEXWGsE2SZ3Qn5F
    os-dev-report

    {
      "pr": {
        "number": 31183,
        "url": "https://github.com/objectstack-ai/objectstack/pull/31183",
        "head": "a6f017e7a7e98df4d7ba715480f0b77c1427f4b5",
        "draft": true,
        "base_merged": "origin/main at 56baf78e0, merged once before the PR"
      },
      "premise_still_valid": true,
      "files_changed": [
        ".changeset/31023-package-delete-actor.md",
        "packages/runtime/src/app-plugin.ts",
        "packages/runtime/src/domains/packages.ts",
        "packages/runtime/src/package-state-host-store.ts",
        "packages/runtime/src/package-state-host-store.test.ts",
        "packages/runtime/src/package-uninstall-store-refusal.integration.test.ts",
        "packages/runtime/src/domains/packages-write-actor-session.test.ts",
        "packages/runtime/src/domains/packages-write-flag-arity.test.ts",
        "packages/runtime/src/domains/packages-write-doors-closed-query.test.ts",
        "packages/runtime/src/domains/packages-uninstall-refuse-before-mutate.test.ts",
        "packages/runtime/src/domains/packages-orgless-grants-capability-gate.test.ts"
      ],
      "line_budget": "319 changed lines (+303 / -16) in 11 files vs merge base 56baf78e0, under the 3000 human-merge threshold (dispatch-gates reading at a6f017e7a)",
      "gates": {
        "head": "a6f017e7a",
        "derived_local_union": [
          "node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
          "node scripts/check-adr-0087-registration.mjs --self-test :: exit 0",
          "node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
          "node scripts/check-closing-keyword-parity.mjs :: exit 0",
          "node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
          "node scripts/check-comment-mask-adoption.mjs :: exit 0",
          "node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0",
          "node scripts/check-comment-mask-corpus.mjs :: exit 0",
          "node scripts/check-dts-emitted.mjs --self-test :: exit 0",
          "node scripts/check-empty-changeset.mjs --base origin/main :: exit 0",
          "node scripts/check-empty-changeset.mjs --self-test :: exit 0",
          "node scripts/check-issue-citations.mjs :: exit 0",
          "node scripts/check-plugin-teardown-shape.mjs :: exit 0",
          "node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0",
          "node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0",
          "node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0",
          "node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0",
          "node scripts/ci/snapshot-next.mjs --self-test :: exit 0",
          "node scripts/docs-audit/check-affected-docs.mjs :: exit 0",
          "node scripts/docs-audit/check-drift-comment.mjs :: exit 0",
          "node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0",
          "node scripts/release-pending-publish.mjs --self-test :: exit 0",
          "pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0",
          "pnpm check:changeset-gate-self-tests :: exit 0",
          "pnpm check:ci-filter-parity :: exit 0",
          "pnpm check:closed-ceremony-text :: exit 0",
          "pnpm check:cross-package-test-inputs :: exit 0",
          "pnpm check:dispatcher-error-vocabulary :: exit 0",
          "pnpm check:doc-authoring :: exit 0",
          "pnpm check:driver-memory-census :: exit 0",
          "pnpm check:dts-closure :: exit 0",
          "pnpm check:dual-build-cjs-loads :: exit 0",
          "pnpm check:durability-log-level :: exit 0",
          "pnpm check:engine-double-contract :: exit 0",
          "pnpm check:error-status-conformance :: exit 0",
          "pnpm check:gitlink-declared :: exit 0",
          "pnpm check:issue-citations :: exit 0",
          "pnpm check:keyed-text-bounds :: exit 0",
          "pnpm check:lean-entry-closure :: exit 0",
          "pnpm check:logger-receiver-detach :: exit 0",
          "pnpm check:nul-bytes :: exit 0",
          "pnpm check:objectql-double-limit :: exit 0",
          "pnpm check:org-identifier :: exit 0",
          "pnpm check:page-declaration-shape :: exit 0",
          "pnpm check:platform-object-tenancy-census :: exit 0",
          "pnpm check:published-files :: exit 0",
          "pnpm check:query-options-erasure :: exit 0",
          "pnpm check:refd-timer-probe :: exit 0",
          "pnpm check:registry-log-declared :: exit 0",
          "pnpm check:rest-log-spy-declared :: exit 0",
          "pnpm check:route-envelope :: exit 0",
          "pnpm check:slot-lookup :: exit 0",
          "pnpm check:sourcemap-no-sources-content :: exit 0",
          "pnpm check:stack-collection-maps :: exit 0",
          "pnpm check:swallow-census-controls :: exit 0",
          "pnpm check:system-context-census :: exit 0",
          "pnpm check:test-source-alias :: exit 0",
          "pnpm check:tier-file-adoption :: exit 0",
          "pnpm check:type-check-coverage :: exit 0",
          "pnpm check:type-check-debt :: exit 0",
          "pnpm check:undeclared-dep-imports :: exit 0",
          "pnpm check:vouch-for-reference-callers :: exit 0",
          "pnpm check:watch-hint-literal :: exit 0",
          "pnpm check:where-matcher :: exit 0"
        ],
        "ran_reconciliation": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran ran.txt :: exit 0 -- 64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN",
        "package": [
          "pnpm --filter @objectstack/runtime test :: exit 0 (554 files, 8923 passed, 19 skipped; lock VERDICT command-exit 0)",
          "pnpm --filter @objectstack/runtime typecheck :: exit 0",
          "eslint packages/runtime --no-inline-config (root lint config, touched package) :: exit 0"
        ],
        "first_run_reds_fixed": [
          "pnpm check:dual-build-cjs-loads :: exit 3 (PREREQUISITE NOT MET, 36 packages without dist) -> pnpm build :: exit 0 -> rerun exit 0",
          "pnpm check:query-options-erasure :: exit 1 (test surface 236 -> 237, an as-any query bag in the new pin) -> cast removed -> exit 0",
          "pnpm check:test-source-alias :: exit 1 (edited test files reach a lazy metadata import) -> module-top import added -> exit 0",
          "pnpm --filter @objectstack/runtime test :: exit 1 (9 failed in 4 files: exact deletePackage request pins now carrying the signed-in actor) -> fixtures updated -> exit 0"
        ],
        "ci": "PR check-runs at a6f017e7a: 18 in_progress, 3 skipped at report time"
      },
      "deviations": [
        "File surface: app-plugin.ts, package-state-host-store.ts and its test are outside the claim's declared surface (the claim says stop on breach). The order's measure-first clause asked for a seam the verb already runs; registering it needs a plugin lifecycle (AppPlugin.start) and the store resolution lives in package-state-host-store.ts. Four existing test files outside the surface carry stale-fixture updates. Seat to confirm.",
        "protocol-package-uninstall.ts (named in the claim) is NOT edited: the verb already forwards actor to each deleteMetaItem and already runs the uninstall-cleanup registry.",
        "Lint ran as the root eslint config over packages/runtime with --no-inline-config, not repo-wide pnpm lint (CI-owned).",
        "test:repo (vitest repo project) not run locally; CI Test Core carries it.",
        "Dogfood battery not run: no ! in the title and no (narrowing) arm."
      ],
      "open_questions": [
        {
          "question": "The card's Expected also asks that the sys_packages delete be attributed in sys_audit_log. sys_packages is not an object: service-package deletes the row with raw SQL through engine.execute, which runs no hooks, so no sys_audit_log row exists for that delete with or without an actor. How should that half land?",
          "options": [
            "A: accept the metadata-row attribution as this card's audit acceptance (each removed row names the person) and file a separate card for a package-lifecycle audit row (install/uninstall/enable/disable)",
            "B: widen this PR: an actor on PackageService.delete plus a new audit writer for the sys_packages delete (a signature change and a new audited surface; Clause-2 would need re-declaring)",
            "C: keep Fixes as is but re-word the PR to Part of #31023 and leave the card open for that half"
          ],
          "recommendation": "A, because the half cannot be met by passing an actor (there is no audited write to attribute), the person is already named on every metadata row the uninstall removes, and a package-lifecycle audit row is a design question of its own"
        },
        {
          "question": "With the clear in the verb, a DELETE of an id that is neither installed nor stored (the door answers 404) now clears a disable record naming that id; before, a 404 wrote nothing. Accept?",
          "options": [
            "A: accept: by the #18877 item-3 ruling a package that no longer exists has no lifecycle state",
            "B: guard it: needs an existence signal on the uninstall-cleanup arguments (a contract change on an exported type)"
          ],
          "recommendation": "A, because B widens an exported type for a record that names nothing installed"
        }
      ],
      "out_of_scope_findings": [
        "carrier: none · noted, not filed: the package-door unit pins whose deletePackage double answers no cleanups (packages-write-flag-arity, packages-write-doors-closed-query) make the door write the disable-state file under the real OS_HOME, since neither file sets OS_HOME; pre-existing test hygiene, not a product defect"
      ],
      "mcp_calls": "0",
      "api_writes": "2 -- relay pr_create (POST /repos/objectstack-ai/objectstack/pulls + POST /repos/objectstack-ai/objectstack/issues/31183/assignees), and this comment via post-stamped (POST /repos/objectstack-ai/objectstack/issues/31023/comments)",
      "security_findings": "none"
    }
  7. objectstack-fleet commented on Nov 7, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#4 · marchtian · session_017ihL2dLQEXWGsE2SZ3Qn5F

    Landed · PR #31183 merged by the merge queue as 4c3182140e4e8de389613befb17a871c49f46ee5.

    Reading Value
    PR state merged, merged_at from the pulls API
    Card state closed completed by Fixes #31023
    origin/main content PACKAGE_STATE_UNINSTALL_CLEANUP in packages/runtime/src/package-state-host-store.ts

    pm:dispatched removed with this record. The sys_packages audit half is #31186. #30999 (uninstall dependent-rule refusal on the same door) is unblocked.

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