Skip to content

Pending-changes drill-in lists the registry stamp _packageId under "Also changed" for a new object draft, as if the author had added it #12594

Closed

Description

@objectstack-fleet

Filing gate: ① — a user-visible defect at a public door, captured in a screenshot by the cloud session of #12517.

Symptom (objectui 737592f, a local HotCRM backend under objectstack dev --fresh, a writable package)

Studio › Publish (or Changes) › the pending-changes sheet › a NEW object draft, expanded. Under "Also changed", the drill-in lists _packageId beside the author's own keys. _packageId is the package stamp the metadata layer writes, not a key the author typed. It is visible in 12517-publish-confirm-light.png on the never-merged evidence branch: https://github.com/objectstack-ai/objectui/blob/a4de5c7d69509c1521ed0f5047f8671417d2da9d/docs/design/studio-redesign/findings/12517/12517-publish-confirm-light.png?raw=true

reach: the Studio pending-changes sheet (DraftChangesPanel), also mounted by Home, DraftPreviewBar and CommitTimeline. Measured on the Studio door only.

Cause (read, not yet proven at the producer)

  • packages/app-shell/src/preview/DraftChangesPanel.tsx (objectui, about :217) passes each body through the spec's stripReadDecorations before diffing. That is the right shape, the one objectui#8181 introduced for _draft / _diagnostics.
  • METADATA_READ_DECORATIONS (objectstack packages/spec/src/kernel/metadata-read-decorations.ts:48) is ['_diagnostics', '_draft'], so _packageId survives the strip.
  • Other underscore stamps the registry writes sit beside it in the same read (_packageVersion, _provenance are destructured together in objectstack packages/core/src/fallbacks/authored-translation-sync.ts:204).

Fix direction (contract-first)

Decide at the producer, not in the panel:

  • either the served draft read stops carrying the registry stamps, or the spec's decoration list names them, so stripReadDecorations removes them for every consumer;
  • a panel-side filter would be a consumer-side narrowing, which the four-axis frame rules out.

Which stamps belong on the list, and whether any consumer reads _packageId off a served draft (the CLI's organization-ownership-apply.ts reads it off served items), is the producer's measurement to make.

Acceptance

  1. A new object draft's drill-in lists only keys the author set.
  2. A pin at the producer: the served draft read, passed through stripReadDecorations, carries no registry stamp.
  3. CONTROL: an authored key whose name starts with an underscore, if the spec allows one, still shows.

Who acts

Triage grades and routes it. The fix most likely belongs to the metadata protocol and spec owners (objectstack), with the objectui panel unchanged. It came out of #12517 and is not part of epic #12446.

Dedupe (2026-10-16, MCP search_issues)

  • objectstack-ai/objectui, "_packageId shown as changed key in pending changes drill-in read decorations stripReadDecorations": 0 hits.
  • objectstack-ai/objectstack, "_packageId read decoration METADATA_READ_DECORATIONS draft diff": 13 hits, none this defect. The nearest, objectstack#4326 (read decorations round-tripping into persisted bodies, closed), covers _diagnostics / _draft.

Filed by seat domain:ui#4 (session session_01SJn1GEkrbbBQkhGC26wEDw) from the out-of-scope finding of the #12517 report.

Activity

  1. added
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Oct 16, 2026
  2. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    First grade · domain:ui · p3 · pm:queue, with a routing note on the fix's side · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-16T01:55Z

    Read in this act: the body in full (0 comments), and objectstack packages/spec/src/kernel/metadata-read-decorations.ts on origin/main.

    • The producer options contradict a declared design. The spec's docblock on METADATA_READ_DECORATIONS says: "Deliberately NOT members, though they share the underscore spelling: the ADR-0010 protection envelope (_lock, …, _provenance, _packageId, _packageVersion, …) … envelope state the write path legitimately carries and merges, and the closed metadata schemas allowlist them precisely so a served document keeps its provenance on re-parse."
      • So both producer-side directions in the body reverse that design: stripping the stamps from the served draft read, or listing them as decorations. Either would need its own ruling.
    • The fix is in this lane. The drill-in's "Also changed" list should diff authored content only. It leaves out the envelope keys the spec declares in MetadataProtectionFields (packages/spec/src/kernel/metadata-protection.zod.ts, exported).
      • That reads a declared contract, so it is not a consumer-side narrowing: nothing served or saved changes.
      • The control in the body's acceptance stands: an authored key that the spec allows to start with an underscore still shows.
      • If the claimant measures a reason the panel cannot take it, it raises pm:retriage and does not pick a producer route itself.
    • Lane: domain:ui (packages/app-shell/src/preview/DraftChangesPanel.tsx).
    • Grade: p3. One spurious key in a pending-changes list. Nothing is written wrong, and no surface reads it as authored.
    • State: pm:queue.

    ⛔ Not a claim.

  3. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (domain:ui lane queue; triage-graded p3, 6206274674)
    Session: session_01SJn1GEkrbbBQkhGC26wEDw
    Account: os-sales (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-12594-drill-in-envelope-keys
    Worktree: objectui-issue-12594 (in the cloud session's own container)
    Domain: domain:ui
    Seat: domain:ui#4
    File surface: packages/app-shell/src/preview/DraftChangesPanel.tsx (the authored-key diff of the drill-in), a new test beside it, metadata-admin/i18n.ts or the panel's i18n source (new keys only, if needed), one .changeset/12594-*.md (stop on breach; explain in the report)
    Container & model: S, mode:cloud, model: opus (cloud on the maintainer's stance 「可以的话,ui 后续开发尽量用云 session,并且要求截图给我看」; lane scope on 「你也可以接ui车道的卡」; batch 5 on 「上限提到 5,提速」, this session's chat, 2026-10-15)
    Clause-②: no
    Responsibility: n/a — a lane finding filed by this seat from objectui#12517
    Thread-read: none
    Serial constraints cleared: objectui#12517 (the posture prop in the same file) landed as PR objectui#12595 (92c2a69). Nothing else of this seat is in flight.

  4. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 12594,
      "status": "done",
      "branch": "claude/issue-12594-drill-in-envelope-keys",
      "pr": 12599,
      "session": "session_016v3WXCXoatAw13mUAHxwJB",
      "premise_still_valid": true,
      "summary": "Option (a), built at the one seam every drill-in list reads. `computeChangeDetail` now removes the ADR-0010 protection envelope from both bodies before it compares them. The keys come from `Object.keys(MetadataProtectionFields)` (`@objectstack/spec/kernel`, the subpath the panel already imports), so the Added/Removed/Also-changed key lists, the field lists and the no-change mark all read the same envelope-free pair. The strip does not go in `unwrapItem` (PM assumption 3 holds, refined): `fetchItemBody` → `unwrapItem` also feeds the security-posture lint and the member-existence probe, and the lint's own docblock says the envelope stays on its input. Producer routes untouched, no `METADATA_READ_DECORATIONS` change, no i18n keys. Measured live on a HotCRM `objectstack dev --fresh` backend with a POSTed package (`com.test.ev12594`). A NEW object draft read serves `_packageId` at the top level and no envelope key on its fields; the published read 404s; on main the sheet shows `+ _packageId` under Also changed, and on this branch it does not. Housekeeping: the shared checkout `/home/user/objectui` had a local branch of this name checked out with no own commits; I detached its HEAD at f5eec63 to delete it, per the order. The feature worktree `/home/user/objectui-issue-12594` is left in place because the PR is not open yet; the two auxiliary worktrees (before, evidence) are removed.",
      "tests": "At HEAD 16a2796. (1) New pin `packages/app-shell/src/preview/__tests__/DraftChangesPanel.protectionEnvelope-12594.test.tsx`: `pnpm exec vitest run --maxWorkers=2 FILE` gives Tests 5 passed (5), VERDICT command-exit 0. (2) Targeted reach set (every DraftChangesPanel*, DraftPreviewBar*, CommitTimeline* test, plus every test file that greps DraftChangesPanel, which includes the StudioDesignSurface publish/changes pins and the objectui#12510, #12517 and #8181 read-decoration pins): Test Files 42 passed (42), Tests 265 passed (265), VERDICT command-exit 0. (3) Ablation A, via objectstack scripts/ablation-replace.mjs: the anchor 'for (const key of PROTECTION_ENVELOPE_KEYS) delete rest[key];' was replaced with a no-op. On-disk proof: anchor 1→0, blob 2fef6f576426→99f137d3e45a. Result: 4 failed, with the spec-read guard green as designed. Restored by trap `git checkout HEAD --`; marker count 0, `git hash-object` equals the HEAD blob, `git diff HEAD` empty. (4) Ablation B, which proves the CONTROL can fail: the strip was replaced with an underscore-prefix filter (blob 2fef6f576426→2b4f10724d35). Exactly 1 failed ('still lists a declared underscore key that is not in the envelope'), 4 passed. Restored the same way; hash matches HEAD, `git diff HEAD` and `git status` empty. No dist build is needed for either ablation: the test imports `../DraftChangesPanel` from source by relative path. (5) Full `packages/app-shell/` suite: NOT MEASURED. Reason: the detached run was killed when the session worker restarted (exit 137) and left no verdict line; CI runs it.",
      "gates": "Exit codes captured before any pipe. `pnpm --filter @object-ui/app-shell type-check` (after building the dependency closure with `pnpm exec turbo run build --filter='@object-ui/app-shell^...' --concurrency=2`, exit 0): exit 0. tsconfig.test.json `--listFilesOnly` lists the new test once. `eslint` on the 2 touched source files: exit 0, 0 errors; the 5 warnings are react-refresh/react-hooks warnings on lines this diff did not write (pre-existing, line numbers shifted). `pnpm --filter @object-ui/app-shell build`: exit 0, dist-completeness ran. `pnpm check:i18n-keys`: 0. `pnpm check:i18n-drift`: 0. `pnpm check:i18n-designer-parity`: not run, because no keys were added. `node scripts/check-changeset-presence.mjs`: 0 ('declares 1 changeset'). `node scripts/check-spec-symbol-derivation.mjs`: 0. `pnpm check:new-line-citations`: 0 (0 new). `scripts/check-nul-bytes.mjs` does not exist in objectui (MODULE_NOT_FOUND, not a gate result); self-scan instead: `grep -naP` control-byte scan over the 3 changed files, exit 1 = no hits. `dispatch-gates.mjs --commands`/`--ran` not run: the script lives only in objectstack and gives objectstack's families, so the objectui list was derived by hand from the dispatch. CI check-runs: none yet, because no PR is open (degraded channel); the PM reads them after opening it.",
      "mcp_calls": "4 — mcp__github__issue_read x2 (get, get_comments on objectui#12594); mcp__claude-code-remote__add_repo x2 (objectstack, hotcrm, access read; both answered read_available, nothing attached). No GitHub write tool. One unauthenticated REST GET (curl) of objectui issue comment 6192878850, a read.",
      "api_writes": "0 REST writes. git push only: claude/issue-12594-drill-in-envelope-keys (empty branch first, then c06d7be, 16a2796) and claude/issue-12594-evidence (b75c910). No PR, no comment, no label.",
      "open_questions": [
        {
          "question": "The open choice (PM assumption 2): how should the drill-in treat the ADR-0010 envelope keys? Measured facts: (i) the spec does NOT split the two kinds. `MetadataProtectionFields` is one flat raw shape, and the registry-stamped vs loader-declared split (`DERIVED_PROVENANCE_KEYS` against the `_lock*` family) lives only inside objectstack `packages/metadata-protocol/src/protocol.ts` and is not exported by the spec. So no local split was invented. (ii) No real flow changes a `_lock*` key through a draft. The save door's `authoredLockFamilyRefusal` answers a draft PUT that sets `_lock: full` with 400 VALIDATION_ERROR ('the metadata API never sets, changes or removes a lock … ADR-0010 §3.5'), measured live on the HotCRM backend. Both reads resolve the lock through the same item-lock resolution.",
          "options": [
            "A (built) — drop every envelope key from the diff, keys from Object.keys(MetadataProtectionFields)",
            "B — drop them from the authored lists but show a real envelope change on its own line, e.g. a lock going from none to full on an update",
            "C — split stamps from lock keys locally"
          ],
          "recommendation": "A, on the four axes. Real business need: no producer changes a lock through a draft (the save door refuses it, measured), so (b)'s line would render for no real flow. Long-term soundness: it reads the spec's declaration with no local list, the same derivation rule `unwrapItem` follows; nothing served or saved changes. AI error resistance: the contract is enforced at the save door (a loud 400), not by a tolerant consumer; (c) would invent a split the spec does not declare. Startup focus: (b) adds a label, i18n keys and a test surface for a flow that does not exist; (c) adds a local fork. If ADR-0010 Phase 2/3 later lets a draft move a lock, (b) becomes a small follow-up on this same seam."
        },
        {
          "question": "Contract conflicts, recorded rather than resolved quietly. (1) os-dev.md says the dev opens the draft PR and posts the os-dev-report comment; this dispatch forbids both (the fleet-write relay gets 403 from this container). I followed the dispatch's degraded channel because the file's write path is the relay that is refused; the PR body is in this report, below. (2) os-dev.md's cleanup step (remove the worktree after the PR opens) is not reached: the feature worktree stays until the PM opens the PR.",
          "options": [
            "PM opens the draft PR from pr_body_draft with assignee os-sales and posts this report on the card"
          ],
          "recommendation": "As the dispatch says."
        }
      ],
      "out_of_scope_findings": [
        "observation only, not filed (not a/b/c: no wrong answer measured): on a NEW object draft the field list shows the system fields the draft read serves (`__search`, `created_at`, `created_by`, `organization_id`, `owner_id`, `owning_business_unit_id`, `updated_at`, `updated_by`) as `+` beside the author's `subject`. These are fields the published object really gets, so the list is not false, only noisy. Visible in the evidence screenshots. carrier: none (承接者:无) · dedupe words: pending changes system fields added new object drill-in",
        "observation only: `FieldSchema` also spreads `MetadataProtectionFields`, but the strip is top-level only. Measured live: a served object draft's fields carry no envelope key, so nothing lists today. carrier: none (承接者:无)",
        "deviation-free note: the CONTROL underscore key is `_unpublished` (declared by `AppSchema` outside the envelope, machine-managed). The spec declares no top-level underscore key that an author types: `lintAuthoredRecordKeys` treats every `_` key as the tooling channel."
      ],
      "deviations": [
        "Strip placed in `computeChangeDetail`, not `unwrapItem` (PM assumption 1 named `unwrapItem` as where keys survive; that holds). Reason: `unwrapItem` also feeds the security-posture lint and the member-existence probe. The panel's `unwrapItem` docblock is updated to say so.",
        "Full app-shell suite NOT MEASURED (worker restart killed the run, exit 137); CI runs it.",
        "Shared checkout `/home/user/objectui` HEAD detached at f5eec63 to delete the pre-existing local branch of this name (it had no own commits), as the order allows."
      ],
      "files_changed": [
        "packages/app-shell/src/preview/DraftChangesPanel.tsx",
        "packages/app-shell/src/preview/__tests__/DraftChangesPanel.protectionEnvelope-12594.test.tsx",
        ".changeset/12594-drill-in-protection-envelope.md"
      ],
      "screenshots": [
        "https://github.com/objectstack-ai/objectui/blob/b75c910b2d764eb0ffde280f702ab77888eae416/docs/design/studio-redesign/findings/12594/12594-before-new-object-light.png?raw=true",
        "https://github.com/objectstack-ai/objectui/blob/b75c910b2d764eb0ffde280f702ab77888eae416/docs/design/studio-redesign/findings/12594/12594-before-new-object-dark.png?raw=true",
        "https://github.com/objectstack-ai/objectui/blob/b75c910b2d764eb0ffde280f702ab77888eae416/docs/design/studio-redesign/findings/12594/12594-after-new-object-light.png?raw=true",
        "https://github.com/objectstack-ai/objectui/blob/b75c910b2d764eb0ffde280f702ab77888eae416/docs/design/studio-redesign/findings/12594/12594-after-new-object-dark.png?raw=true"
      ],
      "line_budget": "n/a",
      "pr_title_draft": "fix(app-shell): the pending-changes drill-in no longer lists the protection envelope as authored keys",
      "pr_body_draft": "(elided: it is the body of objectstack-ai/objectui#12599, opened from it verbatim by the seat)"
    }

    Relayed by seat domain:ui#4 (session_01SJn1GEkrbbBQkhGC26wEDw) from the cloud session's final message (degraded channel: the session pushed and returned pr: null; the seat opened #12599 from pr_title_draft and pr_body_draft, and set pr above).


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · PR #12599 merged as 33fbc86 on main · read 2026-10-16T03:20Z

    probe on origin/main 33fbc86 reading control
    Object.keys(MetadataProtectionFields) in preview/DraftChangesPanel.tsx 1 (PROTECTION_ENVELOPE_KEYS, line 287) no local key list
    withoutProtectionEnvelope( call sites 2, both in computeChangeDetail (lines 316–317) the one diff seam
    stripReadDecorations( in unwrapItem 1, unchanged (line 218) fetched bodies keep the envelope
    DraftChangesPanel.protectionEnvelope-12594.test.tsx, .changeset/12594-drill-in-protection-envelope.md present —

    State change: the card closed as completed through Fixes #12594, and pm:dispatched is removed in this act. The cloud session session_016v3WXCXoatAw13mUAHxwJB is archived. Its two observations stay unfiled, as ruled in the ACCEPT 6206996162.


    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

bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions