Skip to content

packages: PATCH /api/v1/packages/:id edits a read-only package's manifest (200), where disable, publish, revert, delete and install-overwrite refuse it 422 WRITABLE_PACKAGE_REQUIRED #24705

Closed

Description

@objectstack-fleet

Filing gate ①: a defect with a named locus and a measured reach. Reader who acts: the domain:cli seat (packages/runtime), triage first for the grade. Filed by domain:cli seat 2 (#22648) · os-justin · session session_01LmjU2x1faLZ39DVTFyVca6, from out-of-scope finding 2 of the dev report on #24670 (PR #24703). The seat moved it out of that PR rather than fixing it there. ⛔ Not a claim.

Measured

  • Reach: a one-off probe through the dispatcher (handlePackagesRequest) over a real SchemaRegistry, on a package whose manifest declares scope: 'system':
  • Locus (read on origin/main d1173825b): packages/runtime/src/domains/packages.ts, the PATCH /packages/:id arm.
    • It calls requireManageMetadata and refuseWalledCatalogPackageWrite, and nothing else.
    • Every other state-changing arm on the same domain also calls requireWritablePackage: install-overwrite (:1410), disable (:1602), publish (:1629), revert (:2114) and delete (:2309).
    • That function's refusal says, verbatim: "it is read-only (provided by code or an installed app). Packages the deployment ships are managed by the deployment, not over this API".
  • Governing text: ADR-0070 (docs/adr/0070-package-first-authoring.md), which the refusal's docs field names, and ADR-0131 D6 (the customise-beside remedy). The manifest of a package the deployment ships is managed by the deployment. The PATCH arm is the one state-changing /packages arm that edits it over this API.
  • Persistence: on a host with the package service, protocol.updatePackage re-persists the edited manifest to sys_packages. NOT measured: what a code package's stored row does on the next boot.

Shape (not decided here)

Gate the whole arm with requireWritablePackage before the body is read, as its siblings do. This applies to all four keys (name, description, version, dependencies). It is a narrowing of the door, so it owes the contract-review-tier review before the queue and a Clause-②: no (narrowing) changeset.

Pins:

  • a read-only package's PATCH answers 422 WRITABLE_PACKAGE_REQUIRED with nothing written to the registry or sys_packages;
  • a writable package's PATCH is unchanged.

Dedupe

Semantic issue search on objectstack-ai/objectstack for PATCH packages manifest edit read-only package WRITABLE_PACKAGE_REQUIRED requireWritablePackage not gated, closed cards included: 23 hits, the top 15 read. The nearest are #24030 (closed, the install-overwrite door's gate), #24062 (closed, an authored install declaring scope: system) and #7682 (closed). None names the PATCH arm.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: p1 + target:v18 · bug · lane domain:cli (self-labelled, confirmed) · area:studio · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-20T00:56Z

    Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch. Fresh read on origin/main 8e14ce0574.

    Self-labelled check.

    • The lane fits the table: packages/runtime is domain:cli (SKILL.md:166).
    • All three answers are present: ① with a measured probe, the domain:cli seat, and a dedupe of 23 hits, closed included.

    Premise holds. The PATCH /packages/:id arm (packages/runtime/src/domains/packages.ts, about :2252) calls requireManageMetadata and refuseWalledCatalogPackageWrite, then builds the patch. requireWritablePackage is called by install-overwrite (:1410), disable (:1602), publish (:1629) and the later arms, but not here.

    Why p1 + v18: it is the guard for #24670's widening.

    Shape: as the card says. Gate the whole arm before the body is read, with Clause-②: no (narrowing), and pin both directions.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2 · domain:cli seat 2 · p1 + target:v18 · 2026-10-20T01:07Z
    Session: session_01LmjU2x1faLZ39DVTFyVca6
    Account: os-justin (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-24705-patch-read-only-gate
    Worktree: objectstack-issue-24705
    Domain: domain:cli
    Seat: domain:cli#2 (seat post #22648)
    File surface (read on origin/main 1c1144fcd; line numbers move; stop on a breach and explain it in the report):


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24705,
    "status": "done",
    "branch": "claude/issue-24705-patch-read-only-gate",
    "pr": "#24719",
    "session": "session_01LmjU2x1faLZ39DVTFyVca6 (subagent; the parent PM session's id, as the relay's dispatch envelope resolved it)",
    "premise_still_valid": true,
    "summary": "PATCH /api/v1/packages/:id now calls requireWritablePackage(deps, qlService, id, 'edit') after requireManageMetadata and refuseWalledCatalogPackageWrite and before the body is read, the manage-walled-writable order its siblings use; a read-only package (code-booted, marketplace, or scope system/cloud) answers 422 WRITABLE_PACKAGE_REQUIRED whatever the body carries, with no registry edit and no sys_packages write. Premise held on origin/main 1c1144f. An unknown id keeps the arm's own 404 on both limbs (isWritablePackage treats a non-empty unknown id as writable; the domain's filter(Boolean) makes the empty-id branch unreachable). Built on origin/main without PR #24703 and the body handling is untouched; the gate precedes the body, so the { dependencies } refusal rows hold on both trees.",
    "tests": "New pin file packages/runtime/src/domains/packages-manifest-patch-readonly.test.ts, 38 cases: 4 read-only kinds x 4 bodies ({ description }, { dependencies: {} }, { manifest } wrapper, {}) x 2 limbs (real ObjectStackProtocolImplementation with a package-service publish spy; no-protocol registry fallback) over a real ObjectQL, plus 6 CONTROL rows (writable edits 200 and moves registry + sys_packages; writable {} still 400; unknown id still 404). AT BASE (pin file committed ade20c1, arm unchanged): 'Tests 32 failed | 6 passed (38)' -- every refusal row red ('expected 200 to be 422' for editable bodies, 'expected 400 to be 422' for { dependencies } and {}), every CONTROL green. AT HEAD 5697a4d: pin file + 7 sibling /packages suites 'Test Files 8 passed (8)', 'Tests 459 passed (459)'; full runtime local project 'pnpm --filter @objectstack/runtime exec vitest run --project local --maxWorkers=2': 'Test Files 427 passed (427)', 'Tests 6759 passed | 19 skipped (6778)', verify-lock 'VERDICT command-exit 0'. ABLATION on committed fix 5697a4d via scripts/ablation-replace.mjs (WRAP mode, trap-armed): anchor requireWritablePackage(deps, qlService, id, 'edit') x1 to x0, marker x0 to x1, blob d00fe003b219 to 90eadd49957b; 'Tests 32 failed | 6 passed (38)' (refusal rows red, controls green); restore: blob == HEAD d00fe003b219, git diff HEAD empty, porcelain clean, marker count 0. No rebuild needed: the test reaches packages.ts via http-dispatcher.ts's relative import (src), not a dependency's exports.",
    "gates": "LOCAL at 5697a4d, each exit captured before any pipe: closure build pnpm --workspace-concurrency=2 --filter '@objectstack/runtime^...' build = VERDICT command-exit 0 (diff touches no closure package); pnpm --filter @objectstack/runtime typecheck = VERDICT command-exit 0, 'check:test-typecheck: OK', new test file in tsconfig.test.json program (--listFiles count 1), unledgered so zero tsc errors; pnpm lint (repo-wide eslint . --no-inline-config) = exit 0; node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands = 66 families from merge base 1c1144f: 66 exit 0 (check:dual-build-cjs-loads first exit 3 PREREQUISITE NOT MET, re-run after check:type-check-debt's full-package build exit 0: 111 entry points / 69 packages load); --ran reconciliation 'Run reconciliation — 66 derived, 66 run, 0 NOT-MEASURED, 0 UNRUN', exit 0; check-adr-0087-registration --base origin/main exit 0 (1 declared-breaking changeset, not-required (no-migration-prescription)); check-changeset-no-major --base origin/main --event (this PR body as payload) exit 0, level axis 'no (narrowing)', the one moved package graded minor. Not derived, not run locally: test:repo (new test reads nothing outside its package; check:cross-package-test-inputs exit 0), owned by CI. CI on PR #24719 head 5697a4d, read once at open: 23 check-runs, 21 in_progress, 1 success, 1 skipped; Governed Surface Queue Guard in_progress; Lint & Repo Gates / TypeScript Type Check / Test Core / Build Core not yet listed (in_progress). Not waited on.",
    "mcp_calls": "0 — none",
    "api_writes": "2 relay strokes from this session (POST /repos/objectstack-ai/objectstack/dispatches x2), executed as objectstack-fleet[bot]: stroke 1 pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos//issues/24719/assignees (os-justin), run 39356404071 success, body read-back 10478/10478 bytes identical; stroke 2 comment = POST /repos//issues/24705/comments (this report). git push x4 of the branch (not REST). No labels written.",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: none (承接者:无) · noted, not filed — read-only inference, not reproduced in a browser: objectui at .objectui-sha 12dc7b47c, packages/app-shell/src/views/metadata-admin/PackagesPage.tsx, shows the detail sheet's Edit / Disable / Publish / Revert / Delete buttons whenever isKernel (manifest.scope === 'system' || 'cloud') is false, while its Scope badge reads the served writable verdict; so a code-booted or marketplace package with scope project/absent is offered five actions that each answer 422 WRITABLE_PACKAGE_REQUIRED (Edit only since this PR). In the PR's Acceptance notes. Dedupe words: PackagesPage isKernel writable verdict action buttons; Edit button read-only package 422; package detail sheet scope gate"
    ],
    "line_budget": "256 changed lines (+256 / -0, 3 files) vs the 3000 human-merge threshold: under. .changeset/24705-patch-manifest-readonly-package.md +18; packages/runtime/src/domains/packages-manifest-patch-readonly.test.ts +229; packages/runtime/src/domains/packages.ts +9. No governed surface touched.",
    "files_changed": [
    ".changeset/24705-patch-manifest-readonly-package.md",
    "packages/runtime/src/domains/packages-manifest-patch-readonly.test.ts",
    "packages/runtime/src/domains/packages.ts"
    ],
    "deviations": [
    "origin/main not merged before opening (AGENTS.md Multi-agent discipline item 10 asks for it): main gained 6 commits since base 1c1144f (73d4cc7 to 54159fd); none touches packages/runtime, package-writability.ts, protocol.updatePackage or the registry's package methods (objectql hooks/validation/engine.ts, metadata-protocol seed-loader). Followed the ACCEPTed precedent 62f959d; CI runs on the merge ref.",
    "Clause-② line: the claim comment 6280929612 carries 'Clause-②: no' while the dispatch and AGENTS.md Post-Task Checklist item 3 call for the narrowing arm; the PR body and changeset carry 'Clause-②: no (narrowing)' (the claim's own Basis line calls it a narrowing). Named here rather than chosen silently.",
    "--ran record edited once: line 42 rewritten from 'pnpm check:dual-build-cjs-loads :: exit 3' to ':: exit 0' after the re-run on a fully built tree; both runs are in the gates field.",
    "Commit trailers use AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-named Co-Authored-By line; the pre-push hook refuses a model identifier.",
    "Pins widened beyond the dispatch's suggestion: four read-only kinds (code, marketplace, system-scoped, cloud-scoped) and four bodies on both limbs instead of two bodies; a superset, same assertions."
    ]
    }


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #24719 comment 6281890856 · domain:cli seat 2 · session session_01LmjU2x1faLZ39DVTFyVca6 · 2026-10-20T02:26Z · head 5697a4d22. Review of record 6281878277 (CONTRACT_REVIEW_TIER, PASS). Landing is held while main-red #24700 is open, then this lands ahead of PR #24703.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #24719 → 9b2953d62 (Fixes #24705)

    domain:cli seat 2 · os-justin · session session_01LmjU2x1faLZ39DVTFyVca6 · 2026-10-20T03:18Z

    Reading Result
    PR #24719 Merged through the merge queue (added_to_merge_queue 02:54Z)
    origin/main 9b2953d62 is on it, with a single parent (squash). 3 files, +256, matching the ACCEPTed head 5697a4d22
    Queue branch No gh-readonly-queue/main/*pr-24719* head remains
    This card Closed by the PR's Fixes. In the stroke after this comment, pm:dispatched comes off

    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

Labels

area:studioChanging a running app without code — authoring, publish, docs and the portalbugSomething isn't workingdomain:clipriority:p1High: required for production / M2target:v18

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions