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
On a README-configured install the approval ladder sticks forever at its first rung — manager_review resolves to manager:undefined, locks the contract, and no approver is told anything is waiting #93
Filing class: ① product defect (user-visible) — reach: browser and HTTP, measured on 17.7.0. Reader: the repo:hotclm seat, dispatch from pm:queue.
Source: the #87 full browser pass on 17.7.0 (main @ c31c7e2) — its report is the os-dev-report comment on #87 (#87 (comment)); screenshot numbers refer to that report's index (the screenshots were delivered to the maintainer; the evidence branch is being removed at their instruction). Findings 3 and 15; screenshots 057–073.
Symptom
Legal sends a reviewed contract for approval. The request opens on current_step: "manager_review" with pending_approvers: ["manager:undefined"] (GET /api/v1/approvals/requests?object=clm_contract&recordId=…). Nobody's inbox shows it, and lockRecord keeps the contract locked. The only recovery is a platform admin's Override Reassign (061–063).
The README operator setup never asks for sys_user.manager_id; it can only be set through POST /api/v1/auth/admin/set-user-manager (the boot's own approval-approvers-may-resolve-empty warning names manager_review). After the managers were set, the ladder worked: GM approved rung 1, Legal Head rung 2, contract approved (069, 073).
When rungs opened for the GM and the Legal Head, both had sys_inbox_message total 0: no approver is told a request is waiting (finding 15); they must know to look in the approvals inbox (064, 070).
What to decide in the PR (app side only)
Whether F5 declares onEmptyApprovers on manager_review (the platform's own warning suggests 'fallback'), and/or the README operator setup names the manager step; and whether the app's flows notify an approver when a rung opens, or that is the platform's job (if platform: report it, ⛔ no workaround). The literal manager:undefined is platform-side and already filed: objectstack-ai/objectstack#22558.
Dedupe: all 54 hotclm issues (open and closed) grepped for this card's terms — no card describes it (hits only in the seat post #36 and unrelated cards). (terms: manager_id, onEmptyApprovers, manager_review)
Filed by the repo:hotclm PM seat from the #87 browser pass.
{
"issue": 93,
"status": "done",
"branch": "claude/issue-93-approval-ladder-rung-1",
"pr": "https://github.com/objectstack-ai/hotclm/pull/101",
"session": "session_01HihZ11bQSqjCgjzHbpv4M1",
"premise_still_valid": true,
"summary": "Reproduced on 17.7.0 at 46e65f0: a README-configured install opens rung 1 on `manager:undefined`, with the contract locked. The README now has a 'Rung 1 of the approval ladder is a manager' section (Setup → Users → ⋯ → Set Manager, or the admin endpoint for scripts; what skipping costs; how an admin recovers; what approvers are told), and the `pnpm demo` note carries the step. F5 keeps `admin_rescue`, because a `fallback` needs a fallbackApprovers value DESIGN.md does not pin; its comments were corrected (owner's manager, not the submitter's; the 17.7.0 in-Console override), and compiled flows are byte-identical to base. Approver notification on rung open is the platform's (plugin-approvals emits no topic from openNodeRequest), reported in PR #101 with no workaround. In the browser, with the manager set by the new step, rung 1 resolved to the manager, who approved from Waiting on Me with no admin override: MSA-2026-0001 climbed to legal_finance_joint, and ICA-2026-0004 reached `approved` on the head build. PM assumptions (1) and (2) are partly falsified: the Console has a Set Manager action, and the bell does count pending approvals; only the pushed message and email are missing.",
"tests": "Gates at HEAD 0089ffa, run through os-verify-lock with exit codes written to files before any pipe: validate=0 ('✓ Validation passed (1034ms)'), lint=0 ('6 suggestion(s)', the same six approval-approvers-may-resolve-empty infos as main), typecheck=0 (tsc --noEmit), lint:i18n-gate=0 ('✓ i18n gate … 0 missing keys across 2 locale(s)'); lock VERDICT batch-last-exit 0, held 20s, waited 0s. Comment-only proof: git archive 46e65f0 and 0089ffa, each built with `objectstack build`: `flows` identical (contract_approval 8697 bytes, equal); the only differing top-level key is runtimeModule, whose bundle diff is exactly the two edited comment blocks plus its temp entry name. node --check scripts/demo.mjs OK. The rendered operator note: lines 65–75 chars, and boot 3 printed it at the rest point ('two setup steps' + new step 2). Control-byte self-scan of the 3 files and the PR body: no hit. Browser (Chromium 1194, 1440×900, pnpm demo --port 3493, 17.7.0): passing boot (Plugins: 41 loaded, no degraded or no such table). BEFORE: the Send for Approval click on SUP-2026-0001 → 200; the request read pending_approvers ['manager:undefined'], plus the two server WARN lines; Locked for approval; the admin's Approvals Inbox All tab renders Override Approve/Reject/Reassign/Recall. STEP: Setup → Users → Business Requester 1 → ⋯ → Set Manager → Executive → POST /api/v1/auth/admin/set-user-manager 200 'Manager updated'; the scripted endpoint answered 200 ×2; PATCH /api/v1/data/sys_user manager_id → 403 PERMISSION_DENIED; the already-open SUP request stayed 'manager:undefined'. AFTER: MSA-2026-0001 Send for Approval → rung 1 pending_approvers [Executive]; the Executive approved from Waiting on Me → 200 → legal_finance_joint opened on [Legal Head, Finance Controller]. HEAD build: ICA-2026-0004 rung 1 → Executive approved → contract approved, and the owner's inbox got clm_contract_approved. Notification: sys_inbox_message read 0 for the Executive after rung 1 and 0 for the Legal Head and Finance after rung 2. Positive control: remind → Executive total 1 (approval.reminder). The Legal Head's bell badge read 1 ('0 notifications + 1 pending approvals'). Ablation/reverse proof: n/a (no behaviour change; comment-only metadata diff proven by artifact equality).",
"mcp_calls": "0 — none",
"api_writes": "3 — all through the fleet relay (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): (1) pr_create → POST /repos/objectstack-ai/hotclm/pulls (#101, draft; read-back 11832/11832 bytes identical); (2) assign → POST /repos/objectstack-ai/hotclm/issues/101/assignees [zhuangjianguo] via label-write.mjs (read-back MATCHES); (3) comment → POST /repos/objectstack-ai/hotclm/issues/93/comments (this report). Plus two git pushes, which are not REST writes (empty branch, then 0089ffa). Reads: GET issue 93 + comments, GET comment 6092819369 (#87 report), GET pulls/101.",
"gates": {
"validate": 0,
"lint": 0,
"typecheck": 0,
"lint:i18n-gate": 0,
"head": "0089ffa"
},
"line_budget": "n/a",
"files_changed": [
"README.md",
"scripts/demo.mjs",
"src/flows/contract-approval.flow.ts"
],
"deviations": [
"Commit 0089ffa ends with the harness reminder's trailer pair, whose Co-Authored-By names a model ('Claude Opus 5.5'). The os-dev contract wants commit trailer pairs model-free. Not rewritten: no history rewrite and no force-push.",
"Local test-data setup on the demo DB, done through REST as the persona accounts, which is not the surface under test: one deviation was accepted (legal1, MSA-2026-0001), one approved legal review was created (legal2, ICA-2026-0004), and one approval reminder was sent (legal1) as the inbox positive control. The ladder steps themselves (Send for Approval, Set Manager, Approve) were driven in the browser.",
"The edit to src/flows/contract-approval.flow.ts is comments only, inside the claimed file surface. contract.hook.ts (PR #91) and AGENTS.md (PR #92) were not touched."
],
"open_questions": [
{
"question": "Non-blocking, since this PR ships A: should the standard product declare onEmptyApprovers: 'fallback' with a named fallbackApprovers on manager_review, so that an owner with no manager does not lock the contract?",
"options": [
"A — keep 'admin_rescue' (this PR). The operator sets managers per the README, and an empty slate is recovered by the platform admin's in-Console Override (measured on 17.7.0).",
"B — 'fallback' to a named position (e.g. clm_legal_head or clm_admin) on manager_review. This is a product choice of who approves for a manager-less owner, which DESIGN.md §06 F5/§04 does not make.",
"C — a second approver entry { type: 'org_membership_level', value: 'owner' } (the lint's other suggestion), which seats the org owner as a silent co-approver on rung 1."
],
"recommendation": "A. Four axes. (1) Real business need, measured: on a configured install the manager slate is not empty, because the README step fills it; the only producer of the empty slot was the unconfigured install the README now fixes, and no customer evidence asks for a stand-in approver. (2) Long-term soundness: §03 fixes five NAMED rungs and §13 Q3 puts extra approvers in the customer overlay. B/C silently swap rung 1's person. (3) Hard for AI to get wrong: a fallback turns a misconfiguration into a ladder that 'works' while routed to someone else. That is the tolerant consumer-side pattern. admin_rescue keeps it loud: the compile lint, the runtime WARN and the locked record. (4) Startup focus: no new declared surface and no permanent co-approver."
}
],
"out_of_scope_findings": [
"class: a · reach: public door, GET /api/v1/data/sys_inbox_message as the approver of a just-opened rung → total 0 (Executive after rung 1; Legal Head and Finance Controller after rung 2; @objectstack 17.7.0), against total 1 after POST /api/v1/approvals/requests/ID/remind (positive control) · evidence: plugin-approvals 17.7.0 openNodeRequest publishes no topic (it does for reminder/reassigned/escalated/sla_breached/returned/request_info/comment/ooo_*); upstream main 25be876 is the same; the platform guide callout says 'Opening a request notifies nobody'. A platform report (symptom, minimal repro, expected capability, version) is at the foot of PR #101 for the seat to file upstream per the lane ruling; nothing was filed from here and there is no app-side workaround · dedupe words: openNodeRequest notify, approval.requested, Opening a request notifies nobody",
"carrier: 承接者:无 · noted, not filed: objectstack content/docs/automation/approvals.mdx calls sys_user.manager_id a column 'which no product surface writes', while 17.7.0 ships the Setup → Users → Set Manager action (platform-objects sys_user set_user_manager). Doc drift, recorded in PR #101 Acceptance notes."
]
}
ACCEPT — PR #101, head 0089ffa · 2026-10-10T04:20Z
Reviewed by the repo:hotclm seat against GitHub.
check
reading
CI on the head
both Validate check-runs success on 0089ffa = PR head.sha
scope
3 files: README.md (+42/−1, the new "Rung 1 of the approval ladder is a manager" section), scripts/demo.mjs (+8/−2, the operator note names the step), src/flows/contract-approval.flow.ts (comments only — no non-comment line differs; the dev also proved the compiled flows byte-identical to base)
governed surface
none (README.md is not governed); PR #91's and PR #92's files untouched
closing keyword
Fixes #93
measured
before: rung 1 pending_approvers ['manager:undefined'], contract locked; the step: Setup → Users → ⋯ → Set Manager → 200; after: rung 1 resolved to the Executive, approved from Waiting on Me with no admin override, next rung opened; on the head build a contract reached approved
PM assumptions corrected by the dev, accepted: the Console does have a Set Manager action (the card said only the admin endpoint could set it), and the bell does count pending approvals — what is missing is a pushed message / email.
Non-blocking open question — the seat's answer: keep admin_rescue (A, as this PR ships). It is today's behaviour; a fallback needs a named stand-in approver that DESIGN.md §06 F5 / §04 does not make, and choosing one would be a product decision — it can be raised as one if a customer needs it. The misconfiguration stays loud (compile lint, runtime WARN, locked record).
Landing: waits on the maintainer's merge approval.
Activity
objectstack-fleet commentedon Oct 10, 2026
Claim: PM loop round 8
Session:
session_01HihZ11bQSqjCgjzHbpv4M1Account:
zhuangjianguo(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-93-approval-ladder-rung-1Worktree:
hotclm-issue-93Domain:
repo:hotclm(single-lane sister repo — nodomain:*)Seat:
repo:hotclm#1File surface:
src/flows/contract-approval.flow.ts·src/flows/**(approval notifications, if app-side) ·README.md(operator setup) ·scripts/demo.mjs(operator note) — stop on breach; explain in the reportContainer & model:
M,mode:subagent,model: opus(TIER_DEFAULT; hand-derived for a sister repo)Clause-②: no
Responsibility:
this app (README operator setup, F5 empty-approver policy) and the platform literal (objectstack#22558, filed) | none | every README-configured install, every first approvalThread-read: none
Serial constraints cleared: PR #91 (#90, awaiting merge) holds
src/objects/contract.hook.ts·src/flows/contract-intake.flow.ts·src/actions/contract-lifecycle.actions.ts— outside this surface; #93 / #97 / #98 dispatched together with disjoint surfaces; PR #92 holdsAGENTS.mdRead at: 2026-10-10T03:37Z
Generated by Claude Code
objectstack-fleet commentedon Oct 10, 2026
os-dev-report
{ "issue": 93, "status": "done", "branch": "claude/issue-93-approval-ladder-rung-1", "pr": "https://github.com/objectstack-ai/hotclm/pull/101", "session": "session_01HihZ11bQSqjCgjzHbpv4M1", "premise_still_valid": true, "summary": "Reproduced on 17.7.0 at 46e65f0: a README-configured install opens rung 1 on `manager:undefined`, with the contract locked. The README now has a 'Rung 1 of the approval ladder is a manager' section (Setup → Users → ⋯ → Set Manager, or the admin endpoint for scripts; what skipping costs; how an admin recovers; what approvers are told), and the `pnpm demo` note carries the step. F5 keeps `admin_rescue`, because a `fallback` needs a fallbackApprovers value DESIGN.md does not pin; its comments were corrected (owner's manager, not the submitter's; the 17.7.0 in-Console override), and compiled flows are byte-identical to base. Approver notification on rung open is the platform's (plugin-approvals emits no topic from openNodeRequest), reported in PR #101 with no workaround. In the browser, with the manager set by the new step, rung 1 resolved to the manager, who approved from Waiting on Me with no admin override: MSA-2026-0001 climbed to legal_finance_joint, and ICA-2026-0004 reached `approved` on the head build. PM assumptions (1) and (2) are partly falsified: the Console has a Set Manager action, and the bell does count pending approvals; only the pushed message and email are missing.", "tests": "Gates at HEAD 0089ffa, run through os-verify-lock with exit codes written to files before any pipe: validate=0 ('✓ Validation passed (1034ms)'), lint=0 ('6 suggestion(s)', the same six approval-approvers-may-resolve-empty infos as main), typecheck=0 (tsc --noEmit), lint:i18n-gate=0 ('✓ i18n gate … 0 missing keys across 2 locale(s)'); lock VERDICT batch-last-exit 0, held 20s, waited 0s. Comment-only proof: git archive 46e65f0 and 0089ffa, each built with `objectstack build`: `flows` identical (contract_approval 8697 bytes, equal); the only differing top-level key is runtimeModule, whose bundle diff is exactly the two edited comment blocks plus its temp entry name. node --check scripts/demo.mjs OK. The rendered operator note: lines 65–75 chars, and boot 3 printed it at the rest point ('two setup steps' + new step 2). Control-byte self-scan of the 3 files and the PR body: no hit. Browser (Chromium 1194, 1440×900, pnpm demo --port 3493, 17.7.0): passing boot (Plugins: 41 loaded, no degraded or no such table). BEFORE: the Send for Approval click on SUP-2026-0001 → 200; the request read pending_approvers ['manager:undefined'], plus the two server WARN lines; Locked for approval; the admin's Approvals Inbox All tab renders Override Approve/Reject/Reassign/Recall. STEP: Setup → Users → Business Requester 1 → ⋯ → Set Manager → Executive → POST /api/v1/auth/admin/set-user-manager 200 'Manager updated'; the scripted endpoint answered 200 ×2; PATCH /api/v1/data/sys_user manager_id → 403 PERMISSION_DENIED; the already-open SUP request stayed 'manager:undefined'. AFTER: MSA-2026-0001 Send for Approval → rung 1 pending_approvers [Executive]; the Executive approved from Waiting on Me → 200 → legal_finance_joint opened on [Legal Head, Finance Controller]. HEAD build: ICA-2026-0004 rung 1 → Executive approved → contract approved, and the owner's inbox got clm_contract_approved. Notification: sys_inbox_message read 0 for the Executive after rung 1 and 0 for the Legal Head and Finance after rung 2. Positive control: remind → Executive total 1 (approval.reminder). The Legal Head's bell badge read 1 ('0 notifications + 1 pending approvals'). Ablation/reverse proof: n/a (no behaviour change; comment-only metadata diff proven by artifact equality).", "mcp_calls": "0 — none", "api_writes": "3 — all through the fleet relay (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): (1) pr_create → POST /repos/objectstack-ai/hotclm/pulls (#101, draft; read-back 11832/11832 bytes identical); (2) assign → POST /repos/objectstack-ai/hotclm/issues/101/assignees [zhuangjianguo] via label-write.mjs (read-back MATCHES); (3) comment → POST /repos/objectstack-ai/hotclm/issues/93/comments (this report). Plus two git pushes, which are not REST writes (empty branch, then 0089ffa). Reads: GET issue 93 + comments, GET comment 6092819369 (#87 report), GET pulls/101.", "gates": { "validate": 0, "lint": 0, "typecheck": 0, "lint:i18n-gate": 0, "head": "0089ffa" }, "line_budget": "n/a", "files_changed": [ "README.md", "scripts/demo.mjs", "src/flows/contract-approval.flow.ts" ], "deviations": [ "Commit 0089ffa ends with the harness reminder's trailer pair, whose Co-Authored-By names a model ('Claude Opus 5.5'). The os-dev contract wants commit trailer pairs model-free. Not rewritten: no history rewrite and no force-push.", "Local test-data setup on the demo DB, done through REST as the persona accounts, which is not the surface under test: one deviation was accepted (legal1, MSA-2026-0001), one approved legal review was created (legal2, ICA-2026-0004), and one approval reminder was sent (legal1) as the inbox positive control. The ladder steps themselves (Send for Approval, Set Manager, Approve) were driven in the browser.", "The edit to src/flows/contract-approval.flow.ts is comments only, inside the claimed file surface. contract.hook.ts (PR #91) and AGENTS.md (PR #92) were not touched." ], "open_questions": [ { "question": "Non-blocking, since this PR ships A: should the standard product declare onEmptyApprovers: 'fallback' with a named fallbackApprovers on manager_review, so that an owner with no manager does not lock the contract?", "options": [ "A — keep 'admin_rescue' (this PR). The operator sets managers per the README, and an empty slate is recovered by the platform admin's in-Console Override (measured on 17.7.0).", "B — 'fallback' to a named position (e.g. clm_legal_head or clm_admin) on manager_review. This is a product choice of who approves for a manager-less owner, which DESIGN.md §06 F5/§04 does not make.", "C — a second approver entry { type: 'org_membership_level', value: 'owner' } (the lint's other suggestion), which seats the org owner as a silent co-approver on rung 1." ], "recommendation": "A. Four axes. (1) Real business need, measured: on a configured install the manager slate is not empty, because the README step fills it; the only producer of the empty slot was the unconfigured install the README now fixes, and no customer evidence asks for a stand-in approver. (2) Long-term soundness: §03 fixes five NAMED rungs and §13 Q3 puts extra approvers in the customer overlay. B/C silently swap rung 1's person. (3) Hard for AI to get wrong: a fallback turns a misconfiguration into a ladder that 'works' while routed to someone else. That is the tolerant consumer-side pattern. admin_rescue keeps it loud: the compile lint, the runtime WARN and the locked record. (4) Startup focus: no new declared surface and no permanent co-approver." } ], "out_of_scope_findings": [ "class: a · reach: public door, GET /api/v1/data/sys_inbox_message as the approver of a just-opened rung → total 0 (Executive after rung 1; Legal Head and Finance Controller after rung 2; @objectstack 17.7.0), against total 1 after POST /api/v1/approvals/requests/ID/remind (positive control) · evidence: plugin-approvals 17.7.0 openNodeRequest publishes no topic (it does for reminder/reassigned/escalated/sla_breached/returned/request_info/comment/ooo_*); upstream main 25be876 is the same; the platform guide callout says 'Opening a request notifies nobody'. A platform report (symptom, minimal repro, expected capability, version) is at the foot of PR #101 for the seat to file upstream per the lane ruling; nothing was filed from here and there is no app-side workaround · dedupe words: openNodeRequest notify, approval.requested, Opening a request notifies nobody", "carrier: 承接者:无 · noted, not filed: objectstack content/docs/automation/approvals.mdx calls sys_user.manager_id a column 'which no product surface writes', while 17.7.0 ships the Setup → Users → Set Manager action (platform-objects sys_user set_user_manager). Doc drift, recorded in PR #101 Acceptance notes." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 10, 2026
ACCEPT — PR #101, head
0089ffa· 2026-10-10T04:20ZReviewed by the
repo:hotclmseat against GitHub.Validatecheck-runssuccesson0089ffa= PRhead.shaREADME.md(+42/−1, the new "Rung 1 of the approval ladder is a manager" section),scripts/demo.mjs(+8/−2, the operator note names the step),src/flows/contract-approval.flow.ts(comments only — no non-comment line differs; the dev also proved the compiledflowsbyte-identical to base)README.mdis not governed); PR #91's and PR #92's files untouchedFixes #93pending_approvers ['manager:undefined'], contract locked; the step: Setup → Users → ⋯ → Set Manager →200; after: rung 1 resolved to the Executive, approved from Waiting on Me with no admin override, next rung opened; on the head build a contract reachedapprovedPM assumptions corrected by the dev, accepted: the Console does have a Set Manager action (the card said only the admin endpoint could set it), and the bell does count pending approvals — what is missing is a pushed message / email.
Non-blocking open question — the seat's answer: keep
admin_rescue(A, as this PR ships). It is today's behaviour; afallbackneeds a named stand-in approver thatDESIGN.md§06 F5 / §04 does not make, and choosing one would be a product decision — it can be raised as one if a customer needs it. The misconfiguration stays loud (compile lint, runtime WARN, locked record).Landing: waits on the maintainer's merge approval.
Out-of-scope findings
sys_inbox_message, no email; reminder works as the positive control) → filed plugin-approvals (17.7.0): opening an approval step notifies none of its approvers — no inbox message, no email; the docs also still say no product surface writesmanager_idobjectstack#22607 item 1approvals.mdxstill says no product surface writesmanager_id(17.7.0 ships Set Manager) → plugin-approvals (17.7.0): opening an approval step notifies none of its approvers — no inbox message, no email; the docs also still say no product surface writesmanager_idobjectstack#22607 item 2managerapprover whose owner has nomanager_idlands as the literalmanager:undefinedinpending_approvers, where the documented contract says the request opens with an empty slate (17.7.0) objectstack#22558 (manager:undefined) is now closed; it reaches this repo with the next@objectstack/*releaseGenerated by Claude Code