Skip to content

Give an applied duty its owner's business unit, and say when there is none - #184

Merged
os-warren merged 3 commits into
mainfrom
claude/project-thread-xwswdr
Nov 2, 2026
Merged

os-warren merged 3 commits into
mainfrom
claude/project-thread-xwswdr

Conversation

@os-warren

@os-warren os-warren commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

Requested by Warren · project thread

Fixes #179

What was wrong

Step 0 was measured on main 3395a9a with @objectstack 18.2.0 against a live pnpm demo; the write-up is in the project file duly-179-apply-unit/step0.md. Apply catalog read only sys_user_position.business_unit_id. On the demo, 0 of the 12 position rows are anchored, because demoStaff has no unit key. So 13 of 13 applied duties had no unit, and the dispatcher copies that null onto every task. Apply runs its engine as system (buildActionExecutionContext sets isSystem), which means #178's duly_duty_unit_stamp never covers it.

The old rung also took "the first anchored assignment of any position". Those assignments are the platform's capability positions (duly_member and so on), not the catalog's position_code.

The change (seat ruling R-A, S1)

  • src/actions/catalog.handlers.ts: resolveBusinessUnits reads sys_user and sys_user_position once per run with $in. It resolves in this order:
    1. the owner's primary_business_unit_id, the same rung as A member can write source, owner and business_unit on their own task, and so enter governed readings or someone else's #176, A member can create a duty with source catalog, another person as owner and a foreign business unit #178 and the fan-out;
    2. otherwise a position anchor, but only when the anchored rows name exactly one unit;
    3. otherwise no unit.
  • What Apply now returns:
    • each created entry carries business_unit (an id, or null);
    • the result adds unanchored, unanchored_owners and a closed outcome: created, created_unanchored or nothing_created.
  • src/actions/catalog.actions.ts: outcomeMessages for the three outcomes, inherited by the duly_catalog_apply_to_people twin. The console toast now reports the counts. For created_unanchored it says how many duties have no business unit.
  • src/translations/zh-CN.ts: the three messages on both action addresses, and the business_unit help text.
  • src/translations/authored-text.ts: action.outcomeMessages is a data-keyed map, with a verdict that keys each message to outcomeMessages.<outcome>.
  • src/objects/duty.object.ts (Claim amendment 2): the business_unit description now says the field is set on create from the owner's primary unit, that Apply falls back to a lone position anchor, and that otherwise it stays empty. Comment updated to match.
  • src/hooks/duty.hook.ts (Claim amendment 2): comment only. The duly_duty_unit_stamp header now says how Apply resolves its own unit.
  • Unchanged:
    • Sync still writes cadence only.
    • The demo relies on the fallback, with no seed change.
    • The demo admin has no unit by design, so their applied duties are reported as unanchored.

Tests

  • test/catalog-instantiate.test.ts: one test per rung, plus:
    • primary unit wins over an anchor in another unit;
    • two different anchors are not guessed between;
    • an expanded lookup;
    • a mixed run reports only the unit-less owners;
    • a re-run gives nothing_created;
    • the units are read with two queries per run.
  • test/catalog-engine-facade.test.ts: through the real action route, three people in one run cover the primary unit (via a real membership row and the plugin-sharing projection), anchor only, and none, along with the reported result.
  • New test/catalog-apply-unit.test.ts:
    • a demo-seeded kernel applies "Plant compliance officer" to all 12 people plus the admin;
    • every created duty of the 12 is stored in the person's seeded unit;
    • the admin's are null and reported (created_unanchored, unanchored_owners = [admin]);
    • the en and zh outcomeMessages cover each outcome, read only real result keys, and carry the same placeholders.
  • test/duty-provenance.test.ts (Claim amendment): Sami's position stays anchored to Quality, and Apply is now expected to give Sami's primary unit, Operations. This makes it the regression guard for R-A precedence. No assertion was removed.

Gates pass locally: pnpm validate, typecheck, test (46 files, 1297 tests) and build.

Live check (18.2.0, fresh pnpm demo and pnpm demo:zh)

  • The Step 0 run (Greta, Ana and the admin on "Plant compliance officer") created 13 duties. The 11 for demo people are in Riverside Plant; the admin's 2 have no unit and are reported. Before this change, all 13 had no unit.
  • All 12 people on "Quality technician": 54 of 54 duties are in each person's unit, in en and in zh.
  • Anchor only (admin has no primary unit and one anchored row in Northgate Quality): 3 of 3 in Northgate Quality.
  • Primary unit wins: Greta's duly_member row is anchored to Northgate Quality, and her duties still land in Riverside Plant.
  • Ambiguous (two anchors in different units): no unit, reported.
  • Toasts (screenshots in the project file duly-179-apply-unit/verify/shots):
    • en: "Created 7 duties. 7 have no business unit, because none could be determined for their owner. 0 already existed and were skipped." / "Nothing new: all 7 duties already existed." / "Created 5 duties. 0 already existed and were skipped."
    • zh-CN: "已创建 7 条职责。其中 7 条没有业务单元,因为无法确定负责人所属的业务单元。0 条已存在,已跳过。" / "没有新职责:全部 7 条都已存在。" / "已创建 5 条职责。0 条已存在,已跳过。"

🤖 Generated with Claude Code

https://claude.ai/code/session_01JQwxbYga1GGouLwJs9WbD3

… none

Apply catalog read only the position-assignment anchor, and no demo
assignment is anchored, so on 18.2 every applied demo duty had no unit
(13 of 13). Apply runs as system, so the hand-made duty stamp from #178
never covers it.

The handler now resolves the unit in the seat's R-A order: the owner's
primary business unit, then a position anchor only when there is no
primary and the anchored rows name one unit, otherwise none. Each created
entry carries business_unit, the result counts unanchored duties and names
their owners, and a closed outcome picks the console toast through
outcomeMessages (en and zh-CN). Sync is unchanged.

Fixes #179

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQwxbYga1GGouLwJs9WbD3
…gle anchor

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQwxbYga1GGouLwJs9WbD3
…anchor fallback

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQwxbYga1GGouLwJs9WbD3
@objectstack-fleet

Copy link
Copy Markdown
Contributor

席位核对

ACCEPT — PR #184 at head 5c298ecc00cdfddf94990861061e4a0716007bfd (duly lane seat, session session_011HyoSXGRy6L13mYq1Hp4n1, 2026-11-02T07:44Z). Implemented by the #179 card thread session_01JQwxbYga1GGouLwJs9WbD3 under the #179 Claim (comment 6571453201) and amendments 1 and 2.

  • Shape: based on main 358c2ca. Since then main moved to 7e98661 (Let every Duly set read business units, so a unit shows its name #183), which shares no file with this PR; a local git merge-tree against 7e98661 is clean. Fixes #179, Clause-②: no, Metadata-protocol: none.

  • Files:

    • src/actions/catalog.handlers.ts
    • src/actions/catalog.actions.ts
    • src/translations/authored-text.ts (outcomeMessages verdict)
    • src/translations/zh-CN.ts
    • src/objects/duty.object.ts (field description)
    • src/hooks/duty.hook.ts (comment only)
    • the catalog tests and test/catalog-apply-unit.test.ts (new)
    • test/duty-provenance.test.ts (amendment 1)

    All are inside the Claim as amended. No governed path.

  • Gate: check-run verify is success on 5c298ec, and mergeable_state is clean.

  • Seat rulings, as applied:

    • R-A: the primary unit first; a lone anchor only when there is no primary unit; otherwise null, and the run reports it.
    • S1: Sync is unchanged.
    • Amendment 1: the conflicting anchor in duty-provenance stays, and the test now expects the primary unit.
  • Read in the diff:

    • resolveBusinessUnits makes two $in reads per run.
    • The result gains outcome, unanchored, unanchored_owners and a per-entry business_unit.
    • The toast comes from outcomeMessages, translated through the coverage walk.
    • The field description (en and zh) and the stamp hook header now describe both writers.
  • Measured by the thread (fresh demo):

    • 11/11 demo-people duties land in Riverside Plant. Dev Admin's 2 are null and reported as created_unanchored.
    • 54/54 land in their own units in both en and zh.
    • The anchor-only case gives 3/3. Two different anchors, or no anchor, give null and are reported.
    • The toasts were verified in en-US and zh-CN.
  • Landing: direct-merge repo. The seat flips the PR ready and squash-merges it with expected_head_sha pinned.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 2, 2026 07:44
@os-warren
os-warren merged commit 16d2644 into main Nov 2, 2026
1 check passed
@objectstack-fleet

Copy link
Copy Markdown
Contributor

Landed

Seat landing at 2026-11-02T07:45Z (duly lane seat, session session_011HyoSXGRy6L13mYq1Hp4n1). The seat flipped the PR ready through the relay, then squash-merged it with expected_head_sha 5c298ec pinned.

probe reading
PR object merged: true, merge_commit_sha 16d2644
origin/main by content 16d2644, single parent 7e98661. catalog.handlers.ts carries resolveBusinessUnits, and #183's sys_business_unit read is still in permission-sets.ts

State: #179 is closed completed by Fixes #179, and pm:dispatched is removed.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Apply catalog leaves a duty's business_unit empty when the owner's position is not anchored to a unit

2 participants