Skip to content

A write can put a company or office in a contract's department — the department-kind filter shapes the picker, not the write #129

Closed

Description

@objectstack-fleet

Filing class: ① product defect (data integrity) — reach: HTTP, measured on 18.1.0 by the #109 dev (os-dev-report on #109, PR #128). Reader: the repo:hotclm seat, dispatch from pm:queue.

Symptom

Since #109, clm_contract.department is an optional lookup to sys_business_unit filtered (picker only) to active kind = department units. A PATCH naming a company as the department answers 200 and stores it, so a headless caller or an import can put a non-department unit on the contract and into every department report.

Expected (seat's call, from the dev's options)

The #125 pattern with one difference: a conditional validation that runs only when a write sets or changes department (and the value is non-null), refusing a unit whose kind is not department. It does not judge active: the field is an optional reporting field, and a retired department must not block an F12 / F13 renewal that copies it (the picker already hides retired units). Message in both bundles.

Constraints

Dedupe: hotclm issues listed and grepped for department — #109 only (landed).


Filed by the repo:hotclm PM seat from the #109 dev report.

Activity

  1. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 14
    Session: session_017nzWFRUo2U7evMkPdVniAn
    Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-129-department-kind-check
    Worktree: hotclm-issue-129
    Domain: repo:hotclm (single-lane sister repo — no domain:*)
    Seat: repo:hotclm#1
    File surface: src/objects/contract.object.ts (validations, the department comment) · src/translations/{en,zh-CN}/objects.contract.ts — stop on breach; explain in the report
    Container & model: S, mode:subagent, model: opus (TIER_DEFAULT; hand-derived for a sister repo), effort: medium
    Clause-②: no
    Metadata-protocol: none
    Responsibility: this app's clm_contract write rules (department kind) | none known | every contract whose department is written headlessly
    Thread-read: none
    Premise: measured by the #109 dev on 18.1.0 (PATCH naming a company as department → 200); re-measured by the dev on origin/main abd5401
    Serial constraints cleared: PR #125 (the pattern) merged 9d1ad0f; PR #128 (the lookup) merged abd5401. In flight: #95 remainder (descriptions, comments, other bundles) — disjoint files.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 129,
      "status": "done",
      "branch": "claude/issue-129-department-kind-check",
      "pr": "https://github.com/objectstack-ai/hotclm/pull/130",
      "session": "session_017nzWFRUo2U7evMkPdVniAn",
      "premise_still_valid": true,
      "summary": "Added a conditional validation contract_department_set on clm_contract: when = record.department != null && record.department != \"\" && (previous == null || previous.department != record.department); then = cross_field contract_department_is_department refusing record.department.kind != \"department\". It does not judge active. Message in en and zh-CN under objects.clm_contract._validations (the #125 layout); the department field comment now names the rule. The empty-string half of the guard is measured, not decorative: the engine stores a REST \"\" on this lookup as given, and with only the null guard PATCH {department: \"\"} was refused 'could not be evaluated'. File surface held: src/objects/contract.object.ts and src/translations/{en,zh-CN}/objects.contract.ts only. Premise re-measured on abd5401: every door accepted a company and an office as department.",
      "tests": "Metadata-only app, no unit suite; runtime battery on 18.1.0, pnpm demo --port 3129, clean DB per boot, before = abd5401 and after = 32eef5b, both seeds {inserted:830, errored:404}. Persona Business Requester 1 made the README way (admin create-user 200 mustChangePassword:true, persona change-password 200). REQUESTER: headless launch_contract with company / office → before 200 + row, after 400 FLOW_FAILED 'Requesting Department must be a department', 0 rows; ARC-LEGAL / omitted / null → 200 + row both sides. PATCH own draft company / office → before 200 stored, after 400 VALIDATION_FAILED fields[0]={field:department, code:rule_violation}, unchanged; zh-CN header → zh message; ARC-SALES / null / \"\" / summary-only → 200 both sides. /api/v1/batch update company (en) / office (zh-CN) → after 400 with locale message; ARC-SALES 200. POST company → after 400, 0 rows; ARC-SALES 201. ADMIN: POST company / company zh-CN / office → after 400 x3 (zh message on zh call); ARC-FIN / null / \"\" → 201 x3 both sides. PATCH seeded SOW-2026-0004 company / office → after 400; ARC-SALES / null 200. batch create company → after 400, 0 rows. Import insert company / office → before ok=1 created=1, after row ok:false code rule_violation field department (zh-CN call → zh message); ARC-FIN ok=1 both. Import update (writeMode update, matchFields contract_number) to company → before updated=1, after ok:false, unchanged; to ARC-FIN updated=1. Admin launch with company → after 400, 0 rows. RETIRED: requester draft set to PROBE-RETIRE-DEPT (200), unit retired (200), then PATCH title 200, same retired dept re-sent + summary 200, batch summary 200 (same on main). F12 start_renewal on active ICA-2026-0002 after retiring its department ARC-PEOPLE → 200, drafts carrying ARC-PEOPLE 0 → 1 (admin). PRE-RULE COMPANY ROW: three files reverted to abd5401 under the dev watcher (artifact rule refs 0), requester PATCH NDA-2026-0022 department=ARC-EU 200; restored with git checkout HEAD (git diff HEAD empty, three blob hashes match HEAD, artifact rule refs 3); then PATCH title 200, same company re-sent 200, batch summary 200, change to office 400. First revert attempt left the bundle keys in place and the compile was refused translation-target-unknown, so that leg was redone with all three files; the void first reading is not used. BROWSER (Chromium /opt/pw-browsers/chromium, as the requester): on NDA-2026-0022 holding the pre-rule company, Edit → Title → Update sent POST /api/v1/batch data {title} → 200, toast 'Contract updated successfully'; department picker GET sys_business_unit filter [kind=department, active=true] → exactly the 7 seeded active departments, no company, office or retired probe; pick Legal → Update → batch 200, record reads Legal; console only the pre-login 401, one resource 404, and the three pre-existing record.status header predicate warnings. Boot WARN/ERROR signature diff before vs after: 426 vs 425, the one difference is the read-only contract_number drop caused by the import-update probe; 'Requesting Department' 0 lines in the after boot. Ablation: n/a (no test file; the revert leg above is the mutation proof with hash-verified restore).",
      "gates": "head 32eef5b, each exit captured to a file before any pipe: pnpm install exit 0; pnpm validate exit 0 'Validation passed (897ms) 11 Objects 167 Fields'; pnpm lint exit 0 '6 suggestion(s)' (all six approval-approvers-may-resolve-empty, unchanged from main); pnpm typecheck exit 0; pnpm lint:i18n-gate exit 0 'COVERAGE : 0 missing keys across 2 locale(s)'; CLM_DEMO_SEED=1 pnpm validate exit 0; OS_SEED_LOCALE=zh-CN CLM_DEMO_SEED=1 pnpm validate exit 0. hotclm has no dispatch-gates.mjs; the CI family is ci.yml's install/validate/lint/typecheck/lint:i18n-gate, all run locally above. PR check-runs on 32eef5b: Validate completed success.",
      "dogfood_battery": "n/a — sister repo, no `!` in the PR title and no (narrowing) arm (claim: Clause-②: no)",
      "mcp_calls": "0",
      "api_writes": "2 relay strokes — (1) fleet-write dispatch fw-20261025T155257Z-2e55bd: pr_create = POST /repos/objectstack-ai/hotclm/pulls (draft, #130) + POST /repos/objectstack-ai/hotclm/issues/130/assignees [zhuangjianguo], read-back 12959/12959 bytes identical; (2) this os-dev-report comment, POST /repos/objectstack-ai/hotclm/issues/129/comments via post-stamped. git push is not a REST write.",
      "open_questions": [],
      "out_of_scope_findings": [
        "class: a (user-visible copy) · reach: HTTP, measured — POST /api/v1/actions/clm_contract/launch_contract with accept-language: zh-CN and a company as department answers 400 FLOW_FAILED carrying the ENGLISH rule message, while REST, batch and import answer the zh text; the flow's create_record does not carry the caller's locale into the engine's rule-message translation · the same behaviour PR #125 recorded for contract_entity_is_active_company, so it is platform-side and recurring across rules · dedupe words: flow create_record locale, FLOW_FAILED english message, validation message zh-CN flow · carrier: none known — noted in PR #130 Acceptance notes, not filed",
        "observation · REST PATCH/POST {department: \"\"} on a lookup is stored as \"\" rather than null (main and this branch alike); carrier: none — PR #130 Acceptance notes only"
      ]
    }
  3. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed — PR #130 · 2026-10-25

    reading value
    PR #130 MERGED (squash on main), merged at its ACCEPT head 32eef5b; Fixes #129 closes this card
    authority maintainer, project thread, verbatim: 「你可以自己审核的就自己合并」

    Release: a write that sets or changes department to a unit that is not department-kind is refused on every door; retired departments never block an edit or a renewal. The flow door's English message under zh-CN is objectstack-ai/objectstack#25809 (filed from #119). State: pm:dispatched removed, assignee cleared.


    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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions