Skip to content

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

Open

Description

@objectstack-fleet

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.

Activity

  1. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 8
    Session: session_01HihZ11bQSqjCgjzHbpv4M1
    Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-93-approval-ladder-rung-1
    Worktree: hotclm-issue-93
    Domain: repo:hotclm (single-lane sister repo — no domain:*)
    Seat: repo:hotclm#1
    File 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 report
    Container & 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 approval
    Thread-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 holds AGENTS.md
    Read at: 2026-10-10T03:37Z


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  3. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    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.

    Out-of-scope findings


    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions