You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
A write can put a company or office in a contract's department — the department-kind filter shapes the picker, not the write #129
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
Existing rows are not re-judged; an unchanged department never trips the rule.
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/mainabd5401
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.
{
"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"
]
}
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.
Activity
objectstack-fleet commentedon Oct 25, 2026
Claim: PM loop round 14
Session:
session_017nzWFRUo2U7evMkPdVniAnAccount:
zhuangjianguo(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-129-department-kind-checkWorktree:
hotclm-issue-129Domain:
repo:hotclm(single-lane sister repo — nodomain:*)Seat:
repo:hotclm#1File surface:
src/objects/contract.object.ts(validations, thedepartmentcomment) ·src/translations/{en,zh-CN}/objects.contract.ts— stop on breach; explain in the reportContainer & model:
S,mode:subagent,model: opus(TIER_DEFAULT; hand-derived for a sister repo),effort: mediumClause-②: no
Metadata-protocol: none
Responsibility:
this app's clm_contract write rules (department kind) | none known | every contract whose department is written headlesslyThread-read: none
Premise: measured by the #109 dev on 18.1.0 (
PATCHnaming a company as department → 200); re-measured by the dev onorigin/mainabd5401Serial constraints cleared: PR #125 (the pattern) merged
9d1ad0f; PR #128 (the lookup) mergedabd5401. In flight: #95 remainder (descriptions, comments, other bundles) — disjoint files.Generated by Claude Code
objectstack-fleet commentedon Oct 25, 2026
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" ] }objectstack-fleet commentedon Oct 25, 2026
Landed — PR #130 · 2026-10-25
MERGED(squash onmain), merged at its ACCEPT head32eef5b;Fixes #129closes this cardRelease: a write that sets or changes
departmentto 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:dispatchedremoved, assignee cleared.Generated by Claude Code