Repository navigation
feat(cli): os meta delete and os meta register take --package, forwarded as the SDK's packageId (#23350) - #23475
Conversation
…ded as the SDK's packageId `os meta delete --package <id>` forwards DeleteMetaItemOptions.packageId (?package=), the remedy the reset door's 409 RESOURCE_CONFLICT prescribes for an item two packages hold. `os meta register --package <id>` forwards SaveMetaItemOptions.packageId, so a save can name the row it writes. An empty --package is refused before a client exists, as --if-match is. The DeleteMetaItemOptions header and metaDeleteOptions' docblock name the reset door's carriers instead of counting them. Claude-Session: https://claude.ai/code/session_01YRp5z4kK6vgYkrdH97ztsy Co-authored-by: Claude <noreply@anthropic.com>
…or the --package flags Claude-Session: https://claude.ai/code/session_01YRp5z4kK6vgYkrdH97ztsy Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 22 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 9 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 28 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin e9a55fd79ef96a02cedc65abec4b1565cc838170 && git checkout e9a55fd79ef96a02cedc65abec4b1565cc838170
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin cb9841aa253227625738d0e7dbf9add7853fe939 7ec2fb6c717c2095b2645eb234d1d3e0d8ef3845 && git checkout -B drift-repro cb9841aa253227625738d0e7dbf9add7853fe939 && git merge --no-ff 7ec2fb6c717c2095b2645eb234d1d3e0d8ef3845
node scripts/docs-audit/affected-docs.mjs --json cb9841aa253227625738d0e7dbf9add7853fe939
|
Contract reviewServed-tier: The face that owes this review is the declaration alone: the Inputs:
① Derived judgmentsEvery public-surface change the diff implies:
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
席位核对ACCEPT · The review this PR owes is the 1. Shape:
2. Premises measured:
3. Gates:
4. Deviations accepted:
5. Out of scope:
6. Landing: Tier S, through the ordinary queue, once the head is all green. Generated by Claude Code |
Fixes #23350
Clause-②: yes (widening)
What this does
os meta deleteandos meta registereach take an optional--packageflag (a package id, for examplecom.acme.crm) and forward it verbatim as the SDK'spackageId, which the SDK sends as?package=:os meta delete:metaDeleteOptionsmaps--packagetoDeleteMetaItemOptions.packageId, beside--if-matchand--draft. This is the remedy the reset door's409 RESOURCE_CONFLICTprescribes for an item that two or more packages hold.os meta register: the new exportedmetaRegisterOptionsmaps--packagetoSaveMetaItemOptions.packageId, andrun()passes that bag toclient.meta.saveItem. Before this change the call took no options.--package, both commands send the same request as before (pinned on the wire below).--packageis refused before a client exists, the same way an empty--if-matchalready is. For example,--package "$PKG"withPKGunset expands to an empty value, and the SDK would send that as a package-less request. A package-less request is not always refused: for an item one package holds, the door resolves it to that package's row. So the operator's command line would read as naming one package while the server picks the row. This is the CLI's own input refusal. A non-empty id is never reshaped.DeleteMetaItemOptionsinpackages/client/src/index.ts, andmetaDeleteOptionsinpackages/cli/src/commands/meta/delete.ts. Both state whydropStorageandmemberare not sent.Measurements (the dispatch's hypotheses)
All measured on
origin/main08ea7e368(the base of this branch).DeleteMetaItemOptions.packageId, sent bymetaDeleteQueryas?package=behind a truthy guard. The flag forwards the string unchanged:metaDeleteOptions({ package: ' com.qa.alpha ' })answers{ packageId: ' com.qa.alpha ' }. On the real door,--package com.qa.alphaputs?package=com.qa.alphaon the wire, and the door hands the protocolpackageId: 'com.qa.alpha'.SaveMetaItemOptions.packageIdexists and is sent as?package=bymetaSaveQuery.os meta registersaves active: unflagged, it sendsPUT /api/v1/meta/view/NAMEwith no query string and no?mode=, and the row lands withstate: 'active'.409 RESOURCE_CONFLICT. The CLI's--format jsonenvelope carriescode: 'RESOURCE_CONFLICT'andhttpStatus: 409, and its message names both packages. Nothing is written: both rows keep their bodies.RestServerroute, the realObjectStackProtocolImplementationand real SQLitesys_metadatatables (register-package.test.ts, case 2).If-Matchheader plus the query parametersstate,dropStorage,packageandmember. The query half isMETA_ITEM_RESET_PARAMSinpackages/rest/src/rest-server.ts. The actor comes from the session, not the request. The docblocks now name all five rather than counting them.content/docs/**other than the release-ownedreleases/tree mentionsos meta deleteoros meta registerflags.check:cli-examples-parity's table has nometarow.scripts/docs-audit/affected-docs.mjsmapsregister.tsto the phraseos meta registeras a reviewer prompt, not a generated file.check-affected-docs.mjsran green on this diff.Where the edits sit
All inside the claim's file surface:
packages/cli/src/commands/meta/delete.ts: the flag,metaDeleteOptions, its docblock,EMPTY_PACKAGE_DELETE_REFUSALand one example.packages/cli/src/commands/meta/register.ts: the flag,metaRegisterOptions,EMPTY_PACKAGE_SAVE_REFUSAL, thesaveItemoptions and one example.packages/client/src/index.ts: theDeleteMetaItemOptionsheader only. Comment text, no type change.packages/cli/src/commands/meta/delete-reset-carriers.test.tsgains Part 6, plus one title and comment in Part 1 that stated the old count.packages/cli/src/commands/meta/register-package.test.tsis new and uses the same real-door harness..changeset/23350-cli-meta-package-flag.md:@objectstack/climinor;@objectstack/clientpatch, because the docblock ships indist/index.d.ts(measured:grep -c "reads these carriers" packages/client/dist/index.d.tsanswers 1, andfileslistsdist).Tests
Code under test is identical at
7560e7f9eand the head7ec2fb6c7; the only commit between them adds the changeset.The pins (triage
6157686519), each with a negative control, all through the real door:--format jsonreportscode: 'RESOURCE_CONFLICT'andhttpStatus: 409, both rows remain, nopackageIdreaches the protocol, and the wire search is empty.--package com.qa.alpha: alpha's row is removed and beta's remains. The same probe now seespackageIdarrive, and the wire reads?package=com.qa.alpha. A package that holds no row (com.qa.gamma) removes nothing and answersdeleted: false.--package com.qa.alpha: alpha's row gets the new body and beta's is untouched. The negative control is the unflagged two-package save above, refused 409 with nothing written.--packagecomposed with--draftand--if-match(?state=draft&package=com.qa.alphaplus the header);Runs, each under
scripts/pm/os-verify-lock.sh:vitest run --project integrationon the two meta files at7ec2fb6c7:Test Files 2 passed (2),Tests 37 passed (37). Per-file test time on the shared box:delete-reset-carriers8.8 s,register-package7.2 s.pnpm --filter @objectstack/cli exec vitest run --project unitat7560e7f9e:Tests 4278 passed | 29 skipped, with 285 of 287 files passed.test/published-subpath-console.pin.test.tsandtest/published-subpath-hook-body.pin.test.ts. Both refused with "packages/cli is not built", a prerequisite rather than a verdict.pnpm --filter @objectstack/cli build, those two files plustest/vitest-tiers-partition.test.ts:Test Files 3 passed (3),Tests 51 passed (51).pnpm --filter @objectstack/cli typecheck: exit 0, andcheck:test-typecheck: OK.tsc --noEmit --listFileslists both meta test files.pnpm --filter @objectstack/client typecheck, thenvitest runinpackages/client: exit 0,Test Files 51 passed (51),Tests 663 passed (663).os meta deleteoros meta register:test/remote-api-commands.test.tsimports the command classes and is unit-tier.Ablation (one-off, no file kept), on the delete flag's forwarding:
node scripts/ablation-replace.mjs. It replacedif (packageId !== undefined) options.packageId = packageId;withvoid packageId; /* ABLATION-23350: forwarding removed */.614a82e6f310to39e86c3e9afe.diststep applies: the subject is imported relatively fromsrc(./delete.js).Tests 5 failed | 24 passed (29). These five went red:metaDeleteOptionsmapping;614a82e6f310, equal to HEAD, andgit diff HEADempty.Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat7ec2fb6c7: 66 commands, each run and recorded with its exit code before any pipe.--ranreconciliation: "66 derived famil(ies) accounted for — 63 run, 3 NOT-MEASURED (3 DERIVED from a recorded exit 3)".PREREQUISITE NOT METin this checkout (packages outside this card are not built). They are declared to CI:check:skill-examplescheck:dual-build-cjs-loadscheck:i18n-coverageorigin/mainhad moved 3 commits (4e0838d99,8d5b7bb94,b60eb78d3) since the base.packages/spec/src/stack.zod.ts,service-mailboxand two gate files. None of them is inpackages/cliorpackages/client.mainwas not merged before opening. CI runs the merge ref.Acceptance notes
Observations, not filed.
?package=allas naming no package (metaItemPackageBinding, shared with the save door; the SDK'sDeleteMetaItemOptions.packageIddocblock states it). So--package allreaches the door as a package-less request. The flag forwards verbatim by design, and what an id names is the door's decision.packages/client/src/meta-delete-item-carriers.test.ts's header still narrates "three carriers" and then "a fourth carrier joined later". That is a historical note in a test file outside this claim's surface, left as is.delete-reset-carriers.test.tsnow measures 8.8 s of test time on the shared box, under the 10 s line.Generated by Claude Code