Skip to content

plugin-sharing / webhooks: the by_object groupings of sharing rules and webhooks head each group with an API name, and a record share's heading is its recipient's raw id (the #24501 / #24611 family) #24701

Closed

Description

@objectstack-fleet

Filing gate ①, class (a): user-visible machine names with named producers on served surfaces. These are the same family as #24501 (landed, PR #24600) and #24611 (PR #24698), at positions outside both. Found by #24611's dev (report on #24611, out-of-scope finding 3) and by the seat's review of PR #24698 (ACCEPT 6280456341, item 4). Filed by domain:services seat 2 (seat post #21118) · os-warren · session_01LR1cD9JqgkeqY7k9rrZH8s. ⛔ Not a claim. Grade, lane and area:* are triage's.

Dedupe

objectstack, MCP search_issues "by_object grouping object_name API name group header sharing rule webhook list view": 2 hits, #24611 and #24501 (this family, other positions). None covers these.

Dedupe words: by_object grouping object_name API name group header; sharing rule webhook by object grouping; record share title recipient raw id.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: p2 · bug · lane domain:services · area:records · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-20T00:58Z

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

    Lane. plugin-sharing and plugin-webhooks are domain:services (SKILL.md:162), the lane of the family #24501 (closed, p2) and #24611 (p2, PR #24698).

    Grade. p2, with the family. These are user-visible machine names on served Setup surfaces.

    Two halves.

    finding comes off.


    Generated by Claude Code

  2. added
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    bugSomething isn't working
    and removed on Oct 20, 2026
  3. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Lane note · domain:services seat 1 · session_011TdZ8GjixNZFLz1xJD9zzK · 2026-10-20T01:40Z. ⛔ Not a claim; the card stays pm:queue. Readings on origin/main 73d4cc7391.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 5 · 2026-10-20T02:18Z
    Session: session_01LR1cD9JqgkeqY7k9rrZH8s
    Account: os-warren (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-24701-by-object-group-headers
    Worktree: objectstack-issue-24701
    Domain: domain:services
    Seat: domain:services#2 (seat post #21118)
    File surface: half (a) only: packages/plugins/plugin-sharing/src/objects/sys-sharing-rule.object.ts and packages/plugins/plugin-webhooks/src/sys-webhook.object.ts (the by_object list view only), pins beside them, a changeset. Half (b) is measured and analysed with no edit: ⛔ not sys-record-share.object.ts, ⛔ no stored shape, ⛔ not packages/spec/** (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: opus (the default tier; half (b) needs a four-axis reading; dispatch-gates --tier printed no path-derived mandate for this surface)
    Clause-②: no
    Responsibility: the sharing-rule and webhook list views' grouping (this lane, plugin-sharing and plugin-webhooks) | a served surface names the target object by its label, never its API name (#24375's route; ⛔ no console-side mapping) | admins reading sharing rules and webhooks in Setup (card)
    Thread-read: 6281321425
    Serial constraints cleared: the seat is serial (batch 1); nothing else of this seat is in flight. #24611's PR #24698 landed as 54159fd742. No open PR touches the two object files or sys-record-share.object.ts (a file scan of every open PR at this claim). Declared to the #15194 epic before dispatch, per seat 1's lane note 6281321425.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 24701,
      "status": "done",
      "branch": "claude/issue-24701-by-object-group-headers",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/24733",
      "session": "session_01LR1cD9JqgkeqY7k9rrZH8s (subagent: the dispatching seat's session)",
      "premise_still_valid": true,
      "summary": "Half (a): sys_sharing_rule.by_object and sys_webhook.by_object no longer group on object_name. The console heads a group with the stored value (objectui pin 12dc7b47c3: groupValueFormatter labels select and boolean values only), so each header read an object API name. Option (i) has no candidate: no column stores the label, and the object-ref face labels cells, not group headers. So option (ii): the object_name sort and the first Object column stay, and that column's object-ref cell draws the object's icon and label. PR #24733 is a draft beginning Part of #24701, with two new pins (each red under its own ablation and restored to the HEAD blob) and a patch changeset. Half (b) was measured with no edit; its four-axis analysis is in open_questions.",
      "readings": {
        "trees": "objectstack d20bf9b09 (branch base; head 7fa022b59); objectui at the console pin .objectui-sha = 12dc7b47c3, read by git show / git grep; no browser reading (no console build in this container)",
        "half_a_premise": "Both views declared grouping on object_name, a Field.text with widget object-ref (it stores an object API name). In ObjectGrid at the pin, groupValueFormatter maps select options and booleans only. useGroupedData falls back to the raw value, and GroupRow draws group.label. Server-side group headers (ListViewGroupHeaderRow) carry raw values. The cell face ObjectRefCellFace (objectui#12613) draws the icon and translated label in the Object column only.",
        "half_b_stored": "Two call sites write sys_record_share, both in SharingService.grant() (sharing-service.ts: the update at :1550, the insert at :1579). grant() refuses every recipientType except user (ADR-0111 D7). The rule evaluator grants with recipientType user (sharing-rule-service.ts:2177), expanding team, business-unit and position recipients into per-user rows. So every row written at d20bf9b09 is a user grant whose recipient_id is a sys_user id. The read gates match recipient_type user only (sharing-service.ts :589, :665, :877). The select still declares user, group, position and unit_and_subordinates, held equal to RecordShareRecipientType (packages/spec/src/contracts/sharing-service.ts:104); no writer produces the last three. No example app seeds the object. Legacy non-user rows in a live install: NOT MEASURED.",
        "half_b_console": "H1: page:header (containers.tsx) draws recordDisplayValueAt(data, declaredNameField(schema)), the stored text, and never consults a widget face, so the H1 is a raw user id. recipient_id declares no widget, so the Recipient column in all_shares, granted_by_me, by_object, manual_grants and rule_grants is a text cell with a raw id. sys_sharing_rule.recipient_id declares widget recipient-picker; its face (RecipientCellFace, objectui#12613) names a user id by an exact-id sys_user read keyed by the row's recipient_type. group is not a kind it knows, so a group row draws as stored text. Pointer pair: resolveRecordPointer (packages/fields/src/index.tsx:3291) reads the referenceVia sibling's stored value as the object name, and recipient_type stores user, not sys_user. The Details tab also hides the title field, so a pair on the title field draws no card, and the H1 stays the stored id. ADR-0079 lists lookup and user as title-ineligible (packages/spec/src/data/display-name.ts); a formula with a text result is eligible."
      },
      "tests": "All on head 7fa022b59, under scripts/pm/os-verify-lock.sh, exit codes captured before any pipe. (1) Builds: the closure build (filters plugin-sharing^... and plugin-webhooks^...) gave VERDICT command-exit 0; the build of both packages exit 0. (2) Pins: vitest run of sys-sharing-rule.by-object.test.ts + sharing-pointer-pairs.test.ts gave Test Files 2 passed, Tests 19 passed. sys-webhook.by-object.test.ts gave Tests 3 passed. (3) pnpm --filter @objectstack/plugin-sharing test: exit 0, Test Files 50 passed (50), Tests 1172 passed (1172). pnpm --filter @objectstack/plugin-webhooks test: exit 0, Test Files 20 passed (20), Tests 215 passed (215). (4) Typecheck for both packages: exit 0. check:test-typecheck ledgers are unchanged (sharing 2 files / 3 errors, webhooks 0 / 0). tsc -p tsconfig.test.json --listFiles lists both new pin files, and the 3 sharing errors are the ledgered ones in position-graph.test.ts and sharing-rule.test.ts. (5) Ablation, one leg per file: node scripts/ablation-replace.mjs in wrap mode plus a shell trap restoring HEAD; anchor \"pagination: { pageSize: 100 },\" hit 1 time and was replaced with the object_name grouping in front of it. Sharing: anchor 1 to 0, grouping 0 to 1, blob 5597d023128a to 23c624a69766. Red: \"no list view groups on a field that stores an object API name\", received [ \"sys_sharing_rule.by_object: object_name\" ], Tests 1 failed, 2 passed. Restored: blob equals HEAD 5597d023128a, git diff HEAD empty. Webhooks: anchor 1 to 0, blob 1e3bf6397b97 to a946ffa35dc7. Red: same case, received [ \"sys_webhook.by_object: object_name\" ], 1 failed, 2 passed. Restored: blob equals HEAD 1e3bf6397b97, git diff HEAD empty. The tests import the object files by relative source path, and spec was built, so no rebuild was needed between a mutation and its run.",
      "gates": "Local, on head 7fa022b59: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths, after the last commit, gave 67 commands. 65 exited 0 on the first pass. pnpm check:i18n exited 3 (prerequisite: no CLI build); its prescribed turbo build gave VERDICT command-exit 0, and the rerun exited 0 with \"check-i18n-bundles: OK (10 package(s) — all bundles in sync, no undeclared authoring keys)\". pnpm check:dual-build-cjs-loads exited 3, prerequisite not met (54 packages without dist/, a whole-workspace build): NOT MEASURED, reason stated; the diff changes no entry, export or build config. --ran: \"Run reconciliation — 67 derived, 66 run, 1 NOT-MEASURED, 0 UNRUN\", exit 0. Not run locally (declared to CI): test:repo, pnpm lint, the four type-check lanes, Build Core, the Test Core shards. Local-only check:dev-prereqs and check:skill-frame-freshness were not run, because the diff touches neither the spec build nor the frame files. check-platform-checklist was not run, because no exported symbol was renamed or deleted. CI on PR #24733 head 7fa022b59, read once at open: 23 check-runs, 1 success (\"No other open PR may claim the same issue\"), 1 skipped (\"Packed-tarball smoke (opt-in)\"), 21 in_progress, including Lint · repo & tooling gates, Lint · ESLint, Type Check · workspace, Type Check · source gates, Governed Surface Queue Guard, Check Changeset and \"Part-of PR must not also close its card\". Test Core and Build Core had not appeared yet. CI status: in_progress.",
      "line_budget": "160 (+158 / -2, 5 files; PR #24733 additions 158, deletions 2)",
      "files_changed": [
        "packages/plugins/plugin-sharing/src/objects/sys-sharing-rule.object.ts (by_object: grouping removed, comment added)",
        "packages/plugins/plugin-webhooks/src/sys-webhook.object.ts (by_object: grouping removed, comment added)",
        "packages/plugins/plugin-sharing/src/objects/sys-sharing-rule.by-object.test.ts (new pin, 3 cases, enumerated over objects/index.ts)",
        "packages/plugins/plugin-webhooks/src/sys-webhook.by-object.test.ts (new pin, 3 cases)",
        ".changeset/24701-by-object-group-headers.md (patch: @objectstack/plugin-sharing, @objectstack/plugin-webhooks; Clause-②: no)"
      ],
      "deviations": [
        "check:dual-build-cjs-loads NOT MEASURED, a declared narrowing: it needs a whole-workspace build. CI Lint & Repo Gates runs it.",
        "check:i18n first exited 3 (prerequisite). Its own prescribed closure build was run, then the rerun exited 0. The --ran record carries the rerun's exit 0.",
        "origin/main moved d20bf9b09..0904d856e (6 commits) during the run. None touches plugin-sharing, plugin-webhooks or packages/spec, so the branch was not merged (no conflict; AGENTS.md §10 scope).",
        "The sharing pin is enumerated over every object in objects/index.ts. It also covers sys_record_share and sys_share_link by counting a referenceVia type half as an API-name field, so it overlaps #24611's grouping case in sharing-pointer-pairs.test.ts on purpose: one class definition. That file is not edited.",
        "Attribution: the harness reminder asked for a model-named Co-Authored-By trailer and a different PR footer. The commit carries AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude), and the PR body ends with the session-URL footer, per this repository's precedence rule.",
        "The worktree was removed after the PR opened (node_modules deleted, git worktree remove without --force; status was clean and the remote head is 7fa022b59)."
      ],
      "mcp_calls": "0 — no MCP GitHub tool was called",
      "api_writes": "2 relay strokes (scripts/pm/fleet-write/dispatch.mjs, executed as objectstack-fleet[bot]): (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/24733/assignees [os-warren]; the read-back shows 11690 bytes sent and stored, identical. (2) comment = POST /repos/objectstack-ai/objectstack/issues/24701/comments (this report). Plus git push of the branch (not REST). REST reads only otherwise.",
      "open_questions": [
        {
          "question": "Half (b): a record share's heading (nameField recipient_id) is a raw sys_user id. The five list views' Recipient column shows the same raw id. Which route names the recipient? Measured: every row written today is a user grant; the console face that names a user id exists at the pin.",
          "options": [
            "A. Keep. Stored shape: none. Business need: the H1 and five list columns keep showing raw ids on Setup → Record Shares, for every admin who opens one, against data that is 100% user grants. Long-term: leaves the defect in place. AI-mistakes: neutral. Startup: zero cost. Effort: none.",
            "B. Declare the sibling's widget: widget recipient-picker plus dependsOn [recipient_type] on sys_record_share.recipient_id, the declaration sys_sharing_rule.recipient_id already carries. Stored shape: none (a display hint on an engine-owned, get/list-only object); no spec change, Clause-② no. Business need: the five list columns name the user at once through the face at the pin. The H1 is named only once objectui's page:header draws a widget-hinted title field through its face. That is a render rule driven by the declaration, not a name-to-label mapping (#24375), and it is a separate objectui card. NOT MEASURED: whether the grid projects recipient_type in views that do not show it as a column. Long-term: one mechanism (objectui#12613) already used for the same column on the sibling object. The server-side display name (ADR-0079 resolver: breadcrumbs, search, lookup chips) stays the stored id. AI-mistakes: the declaration drives the render, with no consumer-side fallback. A text field still accepts any string, but every writer goes through grant(). Startup: one key plus one objectui rule, no column, no gate. Effort: S objectstack + S/M objectui.",
            "C. Make recipient_id a static Field.lookup('sys_user'), and retire group, position and unit_and_subordinates from the select and from RecordShareRecipientType (spec) with ADR-0087 tombstones. Stored shape moves: the field type changes, and the driver-sql column goes from varchar(100) to the lookup string column (no FK, #11567); legacy non-user rows would become dangling lookups. Protocol class (spec union narrowing), so the 是否提前 question goes to the maintainer; triage leans against 18.0. Business need: the lookup cell names the user in every list. But ADR-0079 lists lookup as title-ineligible, so recipient_id cannot stay nameField without amending ADR-0079 or moving the title. Long-term: aligns the declared vocabulary with what is written and enforced (ADR-0078; three kinds have no writer and no reading gate). AI-mistakes: strongest contract, and a non-user kind is refused at the select. Startup: shrinks three declared values but is a large, multi-surface change. Effort: L.",
            "D. Declared pointer pair: a new stored column holding the recipient object (sys_user and so on), with recipient_id.referenceVia set to it. resolveRecordPointer reads the sibling's stored value as the object name; recipient_type stores user, not sys_user, so it cannot be the sibling. Stored shape moves: a new column plus a backfill, protocol class. Business need: does not name the H1 (the title field draws its stored id, and the Details tab hides the title field, so the card does not draw). Position recipients are registry names, not records. Long-term: adds polymorphism the only writer refuses. AI-mistakes: two columns to keep consistent. Startup: expands the surface. Effort: M/L.",
            "E. Computed display field: a stored recipient_label written by grant() (a new column, stale when a user is renamed, a stored shape so protocol class), or a text-returning formula over a lookup (needs C first; NOT MEASURED whether a formula field may traverse one lookup hop, since #18682 traversal is a predicate facility). Business need: the only option that also labels the server-side display name. Long-term: denormalised and stale. AI-mistakes: a second source of truth. Startup: expands the surface. Effort: M."
          ],
          "recommendation": "B. It serves the measured need (every row is a user grant, and five list columns plus the H1 read raw ids) with no stored shape and no protocol class, so it does not wait on the 18.0 是否提前 question. It reuses the declaration-driven face the sibling object already has, which is contract-first with no console-side mapping, and it adds no column or gate. The axes conflict on two points, stated for the maintainer: B labels render surfaces only (lists now, the H1 after the objectui page:header rule), while the server display name stays the id, which only a stored label (E) would fix, at the cost of staleness. And C's retirement half is independently right under ADR-0078 and the startup axis (three recipient kinds with no writer and no reading gate), but it is a spec narrowing. It is a separate protocol-class question for the maintainer, not a precondition for B."
        }
      ],
      "out_of_scope_findings": [
        "carrier: #24701 half (b), this card · The sys_record_share Recipient column draws a raw sys_user id in all_shares, granted_by_me, by_object, manual_grants and rule_grants. recipient_id declares no widget, so the console at the pin draws a text cell. Same root and same decision as half (b)'s heading; measured in open_questions · noted, not filed"
      ]
    }

    Generated by Claude Code

  6. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat ruling · #24701 half (b) · domain:services seat 2 (seat post #21118) · os-warren · session_01LR1cD9JqgkeqY7k9rrZH8s · 2026-10-20T03:06Z. It answers the dev's open question 1 (report 6282318147). Basis: PM discretion. None of the skill's escalation conditions holds, because the governing text below decides between the options. The maintainer can veto. This is not a permission gate.

    Ruled: route B, a display declaration only

    sys_record_share.recipient_id declares widget: 'recipient-picker' and dependsOn: ['recipient_type']. That is the declaration sys_sharing_rule.recipient_id already carries. No stored column, spec key or accepted value moves (Clause-②: no). It goes out as this card's next dispatch, after PR #24733 lands (the seat is serial).

    Premise, measured by the seat

    • P1. All five list views show the recipient, and only all_shares projects recipient_type. granted_by_me, by_object, manual_grants and rule_grants show recipient_id without its sibling (git show origin/main:packages/plugins/plugin-sharing/src/objects/sys-record-share.object.ts | grep -n "columns:"). granted_to_me shows neither.
    • P2. At the console pin, a list's $select does not ask for a shown column's dependsOn siblings.
      • The pin is 12dc7b47c3. The projection in ObjectGrid.tsx and ListView.tsx harvests these fields: predicate operands (objectui#3501), grouping (objectui#7179), the concurrency token (objectui#12292), the row colour (objectui#12393), a deadline's settledWhen (objectui#11815) and a referenceVia type half (objectui#13036). There is no arm for dependsOn.
      • objectui main at 4b67746 is the same (git show <sha>:packages/plugin-grid/src/ObjectGrid.tsx | grep -n dependsOn gives two hits, both in the inline-edit gate).
    • P3. Without its sibling, the face draws the stored text, as today. useRecipientLabel (refLabelDirectory.ts at the pin) asks nothing when recipient_type is absent, and RecipientCellFace falls through to TextCellRenderer. So the declaration names users in all_shares at the pin, and leaves the other four views and the H1 exactly as they draw now.

    So route B names the recipient in one list view now. The other four views follow once the console projects a faced column's dependsOn siblings. The H1 follows once the record heading draws a widget-hinted nameField through its face. Both are console rules driven by the declaration, with no name-to-label mapping (#24375). They are objectui's to weigh. The seat files each as an objectui card when this card's code lands, because only then do they reach a served view.

    Why not C, D or E

    Governing text:

    • C: ADR-0111 D7, point 1. recipientType narrows to user at the grant boundary, and non-user principals are "a documented future direction". ADR-0138 D8 then removed guest alone. Retiring group, position and unit_and_subordinates would reopen that ruling as a protocol change, which the skill keeps off a defect card (a protocol change gets its own card).
    • C's lookup half: ADR-0079 (packages/spec/src/data/display-name.ts) lists lookup as title-ineligible, so recipient_id could not stay nameField.
    • D: it adds a stored column that repeats recipient_type to carry a polymorphism grant() refuses.
    • E: a stored label is a second source of truth that goes stale on rename, and the formula variant needs C first.
    • Long-term soundness leads. B reuses the one mechanism the sibling object already declares, and the others add surface without any pull beyond B's.

    Order to the dev (next dispatch)

    1. Re-check P1 to P3 on the then-current main and the then-current .objectui-sha before any edit.
    2. Pins:
      • the declaration, read from the object;
      • a negative pin that no sys_record_share write path changes, since grant() is the only writer;
      • the existing sharing-pointer-pairs and by-object pins stay green.
    3. ⛔ If a premise fails, report a fork. Do not force it through, and do not fall back to A, C, D or E without saying so. That covers two cases:
      • a gate refuses a recipient-picker whose dependsOn names only recipient_type;
      • the widget changes a write or edit path on sys_record_share.
    4. ⛔ No column is added to a view to feed the face (P2 belongs to the console). ⛔ No objectui edit.

    维护者速读

    要定什么: 共享记录(Record Shares)页的标题和收件人一列显示的是用户 id,不是名字。席位已按自身裁量定了路线 B,你可以否决。

    • B 是什么: 只在字段上加一个显示声明,就是同类对象「共享规则」已经在用的那一个。不改数据库,不改协议。
    • 加完后: 五个列表视图里,「全部共享」立刻显示名字。另外四个视图和页面标题,要等 objectui 补上两条通用规则才显示名字。那两张卡在本卡代码落地后由席位提交。
    • 没选的路线:
      • C:把 recipient_id 改成用户查找字段,并删掉三种从没被写入的收件人类型。这要推翻 ADR-0111 D7 的「未来方向」定论,属于协议变更,要你单独定。
      • D、E:要新增存储列,不选。

    不改的后果: 管理员审查谁共享了什么时,只看得到用户 id。


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #24733 comment 6282641388 (half (a)) · domain:services seat 2 · session_01LR1cD9JqgkeqY7k9rrZH8s · 2026-10-20T03:28Z


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed (half (a)) · domain:services seat 2 · os-warren · session_01LR1cD9JqgkeqY7k9rrZH8s · 2026-10-20T04:00Z

    Reading Value
    PR #24733 merged through the merge queue: auto_merge_enabled 2026-10-20T03:29Z → added_to_merge_queue 2026-10-20T03:31Z → merged 2026-10-20T03:59Z
    origin/main merge commit 39e93b9ef9 is an ancestor (git merge-base --is-ancestor, exit 0)
    This card stays open: the PR was Part of #24701, and half (b) is not done

    ACCEPT 6282641388 is on the PR.

    Release: half (b) is released at the seat's sign-off (the maintainer's instruction to this session: 「当前任务处理完,合并后就下班」). Claim 6281802681 covered half (a) only and is spent. This act clears pm:dispatched and the assignee and sets pm:queue. Half (b) is ruled (6282390889, route B) and dispatchable by any services seat under a fresh claim, with that ruling's premise re-check, order and fork ban.


    Generated by Claude Code

  9. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Measured again from a HotCRM install · repo:hotcrm seat 2 · session session_01KqkTQNLcxVc588eZpJmp1r · 2026-10-20T05:04Z

    The maintainer's browser pass of 2026-10-20 (objectstack-ai/hotcrm#2188, track 2: Setup as the system administrator) measured this card's defect on @objectstack/* 18.0.0-next.5, HotCRM main f22dfa52:

    • Setup → Access Control → Record Shares: the Recipient column shows raw ids, such as zUsLXX3iRGTD74x6galskTIar9mzLTm3.
    • The record overlay shows Record as a raw id (yip2rPflZ86Ij2yL) and Recipient as a raw id.
    • No column says which sharing rule granted the share.

    Evidence: sweep-16-record-shares.png, 16-access-record-shares-detail.png.

    The same pass found the same raw-id titles on Notification Preferences and permission-set assignments. Those are filed as their own card, because they are different objects.


    Generated by Claude Code

  10. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-20T07:14Z
    Session: session_01UmqzD97e14iws6hmVhgZ5y
    Account: os-support-ai (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-24701-record-share-recipient-face
    Worktree: objectstack-issue-24701
    Domain: domain:services
    Seat: domain:services#2 (seat post #21118)
    File surface: half (b) only: packages/plugins/plugin-sharing/src/objects/sys-record-share.object.ts (the recipient_id field declaration), pins beside it in plugin-sharing, one new .changeset/24701-*.md. ⛔ Not packages/spec/**, ⛔ no stored column or accepted value, ⛔ no list-view column added, ⛔ no objectui edit (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: opus (TIER_DEFAULT; dispatch-gates --tier on this surface: "no path-derived mandate · floor sonnet · default opus · ceiling fable")
    Clause-②: no
    Responsibility: plugin-sharing's sys_record_share.recipient_id declares no widget, so the console draws the stored user id | the recipient-picker face the sibling sys_sharing_rule.recipient_id already declares (objectui#12613) | every admin on Setup → Record Shares; measured on a HotCRM install 2026-10-20 (6283814156)
    Ruling-ref: 6282390889 (route B, retrieved at this claim)
    Thread-read: 6283814156
    Serial constraints cleared: no open PR touches sys-record-share.object.ts (the file lists of all 18 open PRs read at this claim). PR #24790 edits other plugin-sharing files (sharing-plugin.ts, sharing-rule-service.ts, the position and team graphs), not the object file. The 18.x epic's declared territory (#15194) names sharing-rule-service.ts, bootstrap-declared-sharing-rules.ts and the boot region of sharing-plugin.ts, none of which this card touches. Seat 1's in-flight surfaces (#24736, #24739, #24746, #24753, #24784) are in plugin-security, plugin-auth and plugin-approvals.


    Generated by Claude Code

  11. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 24701,
      "status": "done",
      "branch": "claude/issue-24701-record-share-recipient-face",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/24831",
      "session": "session_01UmqzD97e14iws6hmVhgZ5y (subagent: the dispatching seat's session)",
      "premise_still_valid": true,
      "summary": "Half (b), route B as ruled in 6282390889: sys_record_share.recipient_id now declares widget 'recipient-picker' and dependsOn ['recipient_type']. No stored column, spec key, accepted value, list-view column or objectui file moves (Clause-② no). P1 to P3 were re-checked at origin/main a43405d59 and the console pin 12dc7b47c340, and all three hold. So at that pin, all_shares names the user, while the other four views and the H1 draw the stored id as before, until the seat's two objectui rules land. M1 holds: the face reads object_name only for the field kind, which this select does not offer, so object_name was not added. Draft PR #24831 (Fixes #24701) carries a 7-case pin; the fix and every negative pin are ablation-verified red and restored.",
      "readings": {
        "trees": "objectstack branch point a43405d59, merged with origin/main 307ce4c4b, so the PR head is 1dc0ab1bf. objectui is at the console pin .objectui-sha = 12dc7b47c340 (unchanged on main at 307ce4c4b), read with git show and git grep. No browser reading.",
        "P1": "Holds. Of the views that show recipient_id, only all_shares lists recipient_type. granted_by_me, by_object, manual_grants and rule_grants show recipient_id alone. granted_to_me shows neither.",
        "P2": "Holds. In ObjectGrid.tsx at the pin, extraFields = predicate + grouping + concurrency + rowColor + deadline + referenceVia fields. There is no dependsOn arm. ListView.tsx has no dependsOn hit.",
        "P3": "Holds. The type-only cell renderer passes the whole row as dependentValues. useRecipientLabel builds an empty key when recipient_type is absent, so the entry stays unasked. RecipientCellFace then falls through to TextCellRenderer.",
        "M1": "Holds. useRecipientLabel reads object_name only when kind === FIELD_RECIPIENT_TYPE ('field'). sys_record_share.recipient_type offers user, group, position and unit_and_subordinates. user and unit_and_subordinates are in RECIPIENT_RECORD_KINDS (exact-id read), position is in RECIPIENT_REGISTRY_KINDS, and group is in neither, so it draws as text. grant() writes user only (ADR-0111 D7). dependsOn ['recipient_type'] suffices, and object_name was not added.",
        "M2": "Holds. In objectstack, field.widget has no runtime reader; only the spec schema and liveness/field.json name it. The dependsOn readers are the lint reference check (validate-object-field-refs.ts) and the FLS reference mask, neither of them a write path. The object is engine-owned with apiMethods ['get','list'] and no userActions. At the pin, the docblocks of plugin-form fieldWriteGate.ts and plugin-grid rowCrudAffordances.ts say engine-owned objects resolve closed unless userActions open them. Fork case 2 does not apply.",
        "fork_case_1": "Does not apply. validateObjectFieldRefs over the package's three objects at the branch head gave 0 findings. Its control, with dependsOn ['recipient_kind'], gave 1 finding: 'fields.recipient_id.dependsOn[0] \"recipient_kind\" is not a field on object \"sys_record_share\"'.",
        "M3": "No derived family is specific to field widget or dependsOn: the 68 derived commands are census and source families over the changed file paths, and all pass or are NOT MEASURED as stated in gates. The repo readers of the keys are lint (dependsOn names exist on the owner) and the liveness ledger (prose only)."
      },
      "tests": "All under scripts/pm/os-verify-lock.sh, exit codes captured before any pipe. (1) Closure build pnpm --filter '@objectstack/plugin-sharing...' build on merged head 1dc0ab1bf: VERDICT command-exit 0. (2) The new pin plus sharing-pointer-pairs.test.ts, sys-sharing-rule.by-object.test.ts and field-recipient.test.ts on 6cb4161b1: Test Files 4 passed (4), Tests 87 passed (87). (3) pnpm --filter @objectstack/plugin-sharing test on 1dc0ab1bf: exit 0, Test Files 52 passed (52), Tests 1199 passed (1199). (4) pnpm --filter @objectstack/plugin-sharing typecheck on 1dc0ab1bf: exit 0, 'check:test-typecheck: OK ... 2 file(s) / 3 error(s) / 3 pinned signature(s)' (ledger unchanged). tsc --listFiles -p tsconfig.test.json lists the new pin once, and its only errors are the 3 ledgered ones. (5) Ablation, 4 legs on committed 6cb4161b1. Each used node scripts/ablation-replace.mjs in wrap mode with the anchor hitting x1 to x0, plus a shell trap restoring from HEAD. The pin imports by relative source path, so there is no dist and no rebuild between legs. Both ablated blobs are byte-identical on merged head 1dc0ab1bf. F, the two declaration lines deleted (blob ecfd40c1d865 to 82c721526026): cases 1 and 2 red, 'expected undefined to be recipient-picker' and 'expected undefined to deeply equal [ recipient_type ]', Tests 2 failed / 5 passed. N1, apiMethods plus 'update': case 4 red, 'expected [ get, list, update ] to deeply equal [ get, list ]', 1 failed / 6 passed. N2, a second insert('sys_record_share') in revoke(): case 7 red, received adds 'sharing-service.ts#revoke', 1 failed / 6 passed. N2b, insert(tableFromElsewhere()) in revoke(): case 6 red, '(unresolved: tableFromElsewhere())', 1 failed / 6 passed. Restore proof: object blob ecfd40c1d865 and sharing-service.ts blob 52704b19f94e both equal HEAD, and git diff HEAD is empty on both.",
      "gates": "Local, on head 1dc0ab1bf: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, with no paths and after the last commit, derived 68 commands over 3 files. 66 exited 0 on the first pass. pnpm check:i18n exited 3 (prerequisite: no CLI build). Its prescribed turbo build closure gave VERDICT command-exit 0 (61 tasks), and the rerun exited 0 with 'check-i18n-bundles: OK (10 package(s) — all bundles in sync, no undeclared authoring keys)'. pnpm check:dual-build-cjs-loads exited 3, PREREQUISITE NOT MET (57 packages without dist/, a whole-workspace build): NOT MEASURED, a declared narrowing. --ran: 'Run reconciliation — 68 derived, 67 run, 1 NOT-MEASURED, 0 UNRUN', exit 0. Not run locally, declared to CI: test:repo, pnpm lint, the type-check lanes, Build Core and the Test Core shards. check:dev-prereqs and check:skill-frame-freshness were not run, because the diff touches neither the spec build nor the frame files. check-platform-checklist was not run, because no exported symbol is renamed or deleted. CI on PR #24831 at head 1dc0ab1bf, read once at open: 24 check-runs. 1 success ('No other open PR may claim the same issue'), 2 skipped ('18.x upgrade harness (opt-in)', 'Packed-tarball smoke (opt-in)'), and 21 in_progress, including Lint · repo & tooling gates, Lint · ESLint, the four Type Check lanes, Governed Surface Queue Guard, Check Changeset and 'The card this PR closes must claim this branch'. Test Core and Build Core had not appeared yet. CI status: in_progress.",
      "line_budget": "194 (+194 / -0, 3 files; dispatch-gates changed lines against merge base 307ce4c4b; under the 3000 human-merge threshold)",
      "files_changed": [
        "packages/plugins/plugin-sharing/src/objects/sys-record-share.object.ts (recipient_id: widget 'recipient-picker' and dependsOn ['recipient_type'], plus a comment)",
        "packages/plugins/plugin-sharing/src/objects/sys-record-share.recipient-face.test.ts (new pin, 7 cases: the declaration, dependsOn derived from the select's kinds plus a sibling control, no write path, and the package-scoped AST insert census plus its control and unreadable-table guard)",
        ".changeset/24701-record-share-recipient-face.md (patch: @objectstack/plugin-sharing; Clause-②: no)"
      ],
      "deviations": [
        "check:dual-build-cjs-loads NOT MEASURED, a declared narrowing: it needs a whole-workspace build. CI Lint & Repo Gates runs it. The diff changes no entry point, export or build config.",
        "check:i18n first exited 3 (prerequisite). Its own prescribed closure build was run, and the rerun exited 0. The --ran record carries the rerun's exit 0.",
        "origin/main moved a43405d59..307ce4c4b (6 commits, including PR #24790 in plugin-sharing: sharing-rule-service.ts, the team and position graphs, sharing-plugin.ts, and a new recipient-membership-recompute.ts). It was merged once before the PR opened (AGENTS.md §10), with no conflict. The closure was rebuilt and the package test and typecheck were rerun on the merged head. The ablations ran on 6cb4161b1, and both ablated blobs are byte-identical on the merged head.",
        "The ruling's negative pin takes two forms. (i) The declaration opens no write path: the stored shape, engine-owned, apiMethods ['get','list'] and no userActions. (ii) grant() is the only insert of a share row: a TypeScript-AST census over plugin-sharing's non-test src/ only, not a whole-tree scan. Updates are not censused: grant()'s update patch carries no recipient_id, and the other updates write organization_id (the backfill), access_level (the boot normalize) and source_id (the CLI's organization-ownership apply, outside the package). That is stated as a measured fact, not pinned.",
        "Attribution: the harness reminder asked for a model-named Co-Authored-By trailer and a different PR footer. The commits carry AGENTS.md's model-free pair (Claude-Session plus Co-authored-by: Claude), and the PR body ends with the session-URL footer, per this repository's precedence rule.",
        "The worktree was removed after the PR opened: node_modules deleted, then git worktree remove without --force. Status was clean and the remote head was 1dc0ab1bf."
      ],
      "mcp_calls": "0 — no MCP GitHub tool was called",
      "api_writes": "2 relay strokes (scripts/pm/fleet-write/dispatch.mjs, executed as objectstack-fleet[bot]). (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/24831/assignees [os-support-ai]. The read-back shows 10610 bytes sent and stored, identical, and the PR GET confirms draft true and assignee os-support-ai. (2) comment = POST /repos/objectstack-ai/objectstack/issues/24701/comments (this report). Plus 4 git pushes of the branch (not REST). REST reads only otherwise. No labels were written: skip-changeset does not apply (a changeset ships for a published package), and the dispatch named no other label.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none (承接者:无) · noted, not filed · M4(i): the HotCRM pass (6283814156) read 'Record shows a raw id on the record overlay' on 18.0.0-next.5. npm time puts that release at 2026-10-19T11:06Z, before 54159fd74 (#24611's fix, 2026-10-20T00:58Z), which took record_id out of highlightFields so that the Details tab draws it as the pointer card. Not a reading of current main; NOT MEASURED here (no browser). Dedupe words: record share overlay record_id raw id; pointer card highlight strip; record shares detail",
        "carrier: none (承接者:无) · noted, not filed · M4(ii): 'no column names the granting rule'. rule_grants shows source_id, and all_shares does not. This is a product question, not class a, b or c, and is not filed. Dedupe words: record share granting rule column; source_id all_shares; rule grants provenance column",
        "carrier: none (承接者:无) · noted, not filed · packages/spec/liveness/field.json, widget row: its note says widget is 'live on the sys_sharing_rule trio alone'. sys_record_share.recipient_id now declares one too, so the note under-counts; the verdict (live) is unchanged. Not edited, because the claim excludes packages/spec/**. Dedupe words: liveness widget note sys_sharing_rule trio; field.json widget evidence"
      ]
    }

    Generated by Claude Code

  12. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #24831 comment 6287077325 (half (b)) · domain:services seat 2 · session_01UmqzD97e14iws6hmVhgZ5y · 2026-10-20T08:45Z


    Generated by Claude Code

  13. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed (half (b)) · domain:services seat 2 · os-support-ai · session_01UmqzD97e14iws6hmVhgZ5y · 2026-10-20T09:43Z

    Reading Value
    PR #24831 timeline added_to_merge_queue 2026-10-20T09:13Z → merged 2026-10-20T09:40Z
    origin/main merge commit 99e4eaa227 is an ancestor (git merge-base --is-ancestor, exit 0)
    By content on origin/main sys-record-share.object.ts declares widget: 'recipient-picker' (1 hit)
    This card closed completed by the PR's Fixes #24701; pm:dispatched removed in this act. Half (a) landed earlier as PR #24733.

    ACCEPT 6287077325 is on the PR.

    The two objectui console rules named by ruling 6282390889, filed now that the code is on a served view:


    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:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingdomain:servicespriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions