Skip to content

feat(cli): os meta delete and os meta register take --package, forwarded as the SDK's packageId (#23350) - #23475

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-23350-cli-meta-package-flag
Oct 14, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-23350-cli-meta-package-flag

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #23350
Clause-②: yes (widening)

What this does

os meta delete and os meta register each take an optional --package flag (a package id, for example com.acme.crm) and forward it verbatim as the SDK's packageId, which the SDK sends as ?package=:

  • os meta delete: metaDeleteOptions maps --package to DeleteMetaItemOptions.packageId, beside --if-match and --draft. This is the remedy the reset door's 409 RESOURCE_CONFLICT prescribes for an item that two or more packages hold.
  • os meta register: the new exported metaRegisterOptions maps --package to SaveMetaItemOptions.packageId, and run() passes that bag to client.meta.saveItem. Before this change the call took no options.
  • Without --package, both commands send the same request as before (pinned on the wire below).
  • An empty or blank --package is refused before a client exists, the same way an empty --if-match already is. For example, --package "$PKG" with PKG unset 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.
  • The two docblocks the card names now list the reset door's carriers instead of counting them: DeleteMetaItemOptions in packages/client/src/index.ts, and metaDeleteOptions in packages/cli/src/commands/meta/delete.ts. Both state why dropStorage and member are not sent.

Measurements (the dispatch's hypotheses)

All measured on origin/main 08ea7e368 (the base of this branch).

  • H1 (delete): confirmed. The SDK option is DeleteMetaItemOptions.packageId, sent by metaDeleteQuery as ?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.alpha puts ?package=com.qa.alpha on the wire, and the door hands the protocol packageId: 'com.qa.alpha'.
  • H2 (register): confirmed. SaveMetaItemOptions.packageId exists and is sent as ?package= by metaSaveQuery. os meta register saves active: unflagged, it sends PUT /api/v1/meta/view/NAME with no query string and no ?mode=, and the row lands with state: 'active'.
  • H3 (carriers): confirmed, five carriers. The reset door reads the If-Match header plus the query parameters state, dropStorage, package and member. The query half is META_ITEM_RESET_PARAMS in packages/rest/src/rest-server.ts. The actor comes from the session, not the request. The docblocks now name all five rather than counting them.
  • H4 (generated reference): nothing reads these flags.
    • No oclif manifest is generated or committed.
    • No page under content/docs/** other than the release-owned releases/ tree mentions os meta delete or os meta register flags.
    • check:cli-examples-parity's table has no meta row.
    • scripts/docs-audit/affected-docs.mjs maps register.ts to the phrase os meta register as a reviewer prompt, not a generated file. check-affected-docs.mjs ran green on this diff.
    • Nothing to regenerate.

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_REFUSAL and one example.
  • packages/cli/src/commands/meta/register.ts: the flag, metaRegisterOptions, EMPTY_PACKAGE_SAVE_REFUSAL, the saveItem options and one example.
  • packages/client/src/index.ts: the DeleteMetaItemOptions header only. Comment text, no type change.
  • Tests:
    • packages/cli/src/commands/meta/delete-reset-carriers.test.ts gains Part 6, plus one title and comment in Part 1 that stated the old count.
    • packages/cli/src/commands/meta/register-package.test.ts is new and uses the same real-door harness.
  • .changeset/23350-cli-meta-package-flag.md: @objectstack/cli minor; @objectstack/client patch, because the docblock ships in dist/index.d.ts (measured: grep -c "reads these carriers" packages/client/dist/index.d.ts answers 1, and files lists dist).

Tests

Code under test is identical at 7560e7f9e and the head 7ec2fb6c7; the only commit between them adds the changeset.

The pins (triage 6157686519), each with a negative control, all through the real door:

  • Delete, no flag, two packages: --format json reports code: 'RESOURCE_CONFLICT' and httpStatus: 409, both rows remain, no packageId reaches the protocol, and the wire search is empty.
  • Delete, --package com.qa.alpha: alpha's row is removed and beta's remains. The same probe now sees packageId arrive, and the wire reads ?package=com.qa.alpha. A package that holds no row (com.qa.gamma) removes nothing and answers deleted: false.
  • Register, --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.
  • Also covered:
    • the empty-refusal cases, each with a positive control on the same instruments;
    • --package composed with --draft and --if-match (?state=draft&package=com.qa.alpha plus the header);
    • identical bytes through both SDK twins;
    • a save into a new package.

Runs, each under scripts/pm/os-verify-lock.sh:

  • vitest run --project integration on the two meta files at 7ec2fb6c7: Test Files 2 passed (2), Tests 37 passed (37). Per-file test time on the shared box: delete-reset-carriers 8.8 s, register-package 7.2 s.
  • pnpm --filter @objectstack/cli exec vitest run --project unit at 7560e7f9e: Tests 4278 passed | 29 skipped, with 285 of 287 files passed.
    • The 2 failed files were test/published-subpath-console.pin.test.ts and test/published-subpath-hook-body.pin.test.ts. Both refused with "packages/cli is not built", a prerequisite rather than a verdict.
    • After pnpm --filter @objectstack/cli build, those two files plus test/vitest-tiers-partition.test.ts: Test Files 3 passed (3), Tests 51 passed (51).
  • pnpm --filter @objectstack/cli typecheck: exit 0, and check:test-typecheck: OK. tsc --noEmit --listFiles lists both meta test files.
  • pnpm --filter @objectstack/client typecheck, then vitest run in packages/client: exit 0, Test Files 51 passed (51), Tests 663 passed (663).
  • The integration layer beyond the two touched files is declared to CI. No spawn test covers os meta delete or os meta register: test/remote-api-commands.test.ts imports the command classes and is unit-tier.

Ablation (one-off, no file kept), on the delete flag's forwarding:

  • The mutation went through node scripts/ablation-replace.mjs. It replaced if (packageId !== undefined) options.packageId = packageId; with void packageId; /* ABLATION-23350: forwarding removed */.
    • Anchor count 1 to 0; blob 614a82e6f310 to 39e86c3e9afe.
  • No build or dist step applies: the subject is imported relatively from src (./delete.js).
  • The run: Tests 5 failed | 24 passed (29). These five went red:
    • the metaDeleteOptions mapping;
    • NAMING ONE (exit 1, the 409, where 0 was expected);
    • NAMING A PACKAGE THAT HOLDS NO ROW;
    • the composition case;
    • the in-step twins case.
  • As predicted, two cases stayed green: NAMING NONE (the negative control) and the empty-refusal case. In the empty-refusal case, a package-less delete of a one-package item resolves to that package anyway.
  • Restored: blob back to 614a82e6f310, equal to HEAD, and git diff HEAD empty.

Gates

  • Derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 7ec2fb6c7: 66 commands, each run and recorded with its exit code before any pipe.
  • --ran reconciliation: "66 derived famil(ies) accounted for — 63 run, 3 NOT-MEASURED (3 DERIVED from a recorded exit 3)".
  • The three exit-3 gates are PREREQUISITE NOT MET in this checkout (packages outside this card are not built). They are declared to CI:
    • check:skill-examples
    • check:dual-build-cjs-loads
    • check:i18n-coverage
  • The derivation warned that origin/main had moved 3 commits (4e0838d99, 8d5b7bb94, b60eb78d3) since the base.
    • They touch packages/spec/src/stack.zod.ts, service-mailbox and two gate files. None of them is in packages/cli or packages/client.
    • So main was not merged before opening. CI runs the merge ref.

Acceptance notes

Observations, not filed.

  • The door reads ?package=all as naming no package (metaItemPackageBinding, shared with the save door; the SDK's DeleteMetaItemOptions.packageId docblock states it). So --package all reaches 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.ts now measures 8.8 s of test time on the shared box, under the 10 s line.

Generated by Claude Code

…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>
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Oct 14, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/cli, @objectstack/client, touching 14 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/client/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

22 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json cb9841aa253227625738d0e7dbf9add7853fe939.

⛔ 9 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/client/src/index.ts) — pages documenting those are invisible to this run
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 28 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json cb9841aa253227625738d0e7dbf9add7853fe939 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from e9a55fd79ef96a02cedc65abec4b1565cc838170 — the merge of head 7ec2fb6c717c2095b2645eb234d1d3e0d8ef3845 into base cb9841aa253227625738d0e7dbf9add7853fe939, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs cb9841aa253227625738d0e7dbf9add7853fe939 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 7ec2fb6c717c2095b2645eb234d1d3e0d8ef3845
Local-runs: none

The face that owes this review is the declaration alone: the Clause-②: yes (widening) line. The PR touches no published schema (nothing under packages/spec/src/**) and no governed path, so the lane seat writes this record in-seat.

Inputs:

① Derived judgments

Every public-surface change the diff implies:

  • os meta delete --package ID: right. It forwards verbatim as DeleteMetaItemOptions.packageId (?package=) through metaDeleteOptions, and combines with --draft and --if-match.
  • os meta register --package ID: right. It forwards verbatim as SaveMetaItemOptions.packageId through the new metaRegisterOptions. That function returns undefined when the flag is unset, so an unflagged register still sends the active save with no query string. That byte-for-byte equality is pinned on the wire.
  • An empty or blank --package is refused before a client exists, on both commands: right. This matches the existing empty --if-match refusal. Both docblocks give the reason, and it holds against the measured door: a package-less request is resolved to one row, not refused, when one package holds the item. A degrade-to-package-less path would write or remove a row the operator did not name. This goes beyond triage's pins, and it narrows nothing that worked: an empty --package never existed.
  • The carrier docblocks: right. DeleteMetaItemOptions and metaDeleteOptions now name the door's five carriers (If-Match, state, package, dropStorage, member) instead of counting them. They keep the dropStorage ruling (cf71d73), and they record member as an undeclared SDK member, not as a ruling.
  • No published package entry moves. metaRegisterOptions and the two refusal constants are exported from command modules, the convention metaDeleteOptions already follows, not from a package exports entry. @objectstack/client changes docblock text only.

② Semver level

  • The changeset bumps @objectstack/cli to minor, which is right for a widening: two new optional flags.
  • It bumps @objectstack/client to patch, which is right for docblock text with no type or behaviour change.
  • It carries Clause-②: yes (widening), the same as the claim and as PR body line 2.
  • No ADR-0087 disposition is owed: nothing narrows or retires.

③ Boundary flags

  • Open questions: the dev report has none.
  • H2, measured: register saves active. A package-less active save of an item two packages hold is refused 409 RESOURCE_CONFLICT with nothing written, which is the case the flag answers.
  • H4, measured: no generated CLI reference reads these flags. Nothing was regenerated.
  • Out-of-scope findings:
    • Two class-(a) findings, measured at a public door, are filed by the seat as their own cards:
      • the human output of os meta delete says "Metadata deleted" when the door answered reset: false;
      • os meta register cannot send the DESTRUCTIVE_CHANGE 409's ?force=true remedy.
    • Two notes stay Acceptance notes (承接者:无):
      • --package all reaches the door as package-less. That is by design: verbatim forwarding.
      • A historical test header narrates "three carriers".
  • Check-runs on the head, at this read: 16 success, 6 skipped, 15 in progress, 0 failures. The flip waits for an all-green head.

Implemented-by: claude/issue-23350-cli-meta-package-flag
Reviewed-by: session_01YRp5z4kK6vgYkrdH97ztsy

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:spec seat 1 (#6017) · huangyiirene · session session_01YRp5z4kK6vgYkrdH97ztsy · head 7ec2fb6c7 · 2026-10-14T12:08Z

The review this PR owes is the TIER_DEFAULT record for the declaration face. It is posted as 6167942702 (PASS, on this head). Card #23350, claim 6166598761.

1. Shape:

  • PR: draft, base main. Line 1 is Fixes #23350 and line 2 is Clause-②: yes (widening); no other closing keyword sits beside a card number.
  • Size and gate: 6 files, +739 / -13. check-governed-merges --pr 23475 reads NOT governed (752 lines).
  • Changeset: @objectstack/cli minor, @objectstack/client patch. Its "Unchanged" paragraph matches the code: unflagged requests are byte-identical (pinned on the wire), and an empty --package is refused before a request.

2. Premises measured:

  • H1: verbatim forwarding to packageId.
  • H2: register saves active, and a package-less active save of a two-package item is refused 409 RESOURCE_CONFLICT with nothing written.
  • H3: the five carriers are now named, not counted.
  • H4: no generated CLI reference reads the flags.

3. Gates:

  • CI check-runs on 7ec2fb6c7 at this act: 16 success, 6 skipped, 15 in progress, 0 failures. The flip waits for green.
  • Local: dispatch-gates --ran reports 66 families, 63 run and 3 NOT MEASURED (exit 3, unbuilt packages outside the card).
  • Tests: cli integration on both files, 37/37; the cli unit layer, 4278 passed (2 files were prerequisite-refused, then passed once built); client 663/663.
  • Ablation: one, on the delete flag's forwarding. It turned 5 tests red, and the restore was verified.

4. Deviations accepted:

  • The empty---package refusal on both commands, beyond triage's pins. The reason is measured and stated.
  • main was not merged before the PR opened. The 3 incoming commits do not touch packages/cli or packages/client.
  • A stray run-gates.pid was written into the shared checkout and has been removed. Nothing from it was committed.

5. Out of scope:

6. Landing: Tier S, through the ordinary queue, once the head is all green.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 14, 2026 12:24
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 14, 2026
Merged via the queue into main with commit a1285f2 Oct 14, 2026
41 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-23350-cli-meta-package-flag branch October 14, 2026 12:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

2 participants