Skip to content

Fan-out failure notice says "Owner is required" for a removed or banned assignee: name the real cause #160

Closed

Description

@objectstack-fleet

Follow-up to #148 (PR #158).

What happens

Since #158, the assignment fan-out refuses an assignee whose account was removed or banned. It reports the refusal through the existing notice to the assigner (duly.assignment_fanout_failed), whose reason line carries the engine's message. For this case that message is "Owner is required". It is true, but it doesn't tell the assigner what to do: the cause is that the person's account no longer exists or is banned, and the fix is to replace the name or restore the account.

What done looks like

  • The flow passes a cause to the template when the account lookup missed or found a banned account. The template (en-US floor and zh-CN, per Email bundles carry no en-US floor: a recipient whose locale is neither en nor zh-CN dead-letters with TEMPLATE_NOT_FOUND #145's shape) renders a sentence that names the cause and the remedy. Every other failure keeps the engine's reason.
  • Both locales pass the i18n gates. Tests cover a removed account, a banned account and an ordinary bad row, and assert the notice text.
  • Surface: src/flows/assignment.flow.ts, src/email-templates/assignment.email-template.ts, test/assignment-fanout.test.ts, test/email-templates.test.ts.

Size S. Clause-②: no. Metadata-protocol: none.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3
    Session: session_011HyoSXGRy6L13mYq1Hp4n1
    Account: os-warren (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-4wtnso (platform-assigned card-thread branch, at origin/main 25743ed)
    Worktree: duly-issue-160
    Domain: n/a. objectstack-ai/duly is a single-lane app repo with no domain:*.
    Seat: repo:duly#1
    File surface: src/flows/assignment.flow.ts, src/email-templates/assignment.email-template.ts, test/assignment-fanout.test.ts, test/email-templates.test.ts. Stop on any breach until the seat posts a Claim amendment.
    Container & model: S, mode:thread, model: TIER_DEFAULT (hand-derived), effort: medium
    Clause-②: no
    Metadata-protocol: none
    Responsibility: the app. This is notice wording in duly's own flow and template.
    Thread-read: none
    Premise: holds. Since #158 (f26fc5c), a refused removed or banned assignee reaches the assigner with the engine reason "Owner is required".
    Serial constraints cleared: #128 is in flight on phrases, samples and catalog actions, and #146 will be on src/actions/task.*. Neither intersects this surface.


    Generated by Claude Code

  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim correction (2026-11-02T02:50Z): the Branch line should read "at origin/main f26fc5c", not 25743ed. The seat's claim template carried a stale base. The card thread works from f26fc5c, which includes #158 and #159.


    Generated by Claude Code

  3. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim amendment (2026-11-02T02:53Z): the file surface adds src/translations/authored-text.ts, for exactly one machine-path entry, 'flow.nodes[].config.assignments'. The fan-out's cause reset and judge are the repo's first assignment nodes, so that slot is a new string-bearing path, and test/i18n-coverage.test.ts refuses it until it is classified. No open card claims authored-text.ts: #128 is on zh-CN.ts, and #146 may touch en.ts. No serial constraint changes.


    Generated by Claude Code

  4. os-warren commented on Nov 2, 2026

    @os-warren
    Collaborator

    os-dev · session_01JQwxbZpRKVtAMntNgyvxx6 · 2026-11-02T03:01Z
    os-dev-report

    {
      "issue": 160,
      "status": "done",
      "branch": "claude/project-thread-4wtnso",
      "pr": "https://github.com/objectstack-ai/duly/pull/163",
      "session": "session_01JQwxbZpRKVtAMntNgyvxx6",
      "premise_still_valid": true,
      "summary": "Premise holds: on main f26fc5c a removed or banned assignee reaches the assigner as 'Owner is required'. The card's suggested mechanism (one template rendering a cause sentence) is falsified on 18.2.0. The plugin-email renderer only fills {{path}} and {{path | formatter}} holes, with no conditional, and NotifyConfig.template is a raw z.string(). So, with seat approval: the try region clears fanout_refusal_cause first every iteration and judges it straight after the account lookup (account_removed or account_banned, empty otherwise, a blank handle stays empty). The catch region is an exclusive gateway onto three notices. Two are new bundles (duly.assignment_fanout_account_removed and duly.assignment_fanout_account_banned, each with an en-US floor and zh-CN rows) that name the cause and the remedy. The third is the unchanged duly.assignment_fanout_failed with the engine reason. All three share topic duly.assignment_fanout_failed. The owner guard from #158 is untouched. Surface: the four claimed files plus one OPAQUE entry in src/translations/authored-text.ts, added by Claim amendment because the first assignment node opens a new string-bearing slot.",
      "tests": "Head 25cd82a. pnpm test EXIT=0: Test Files 38 passed (38), Tests 948 passed (948). New real-engine cases assert the exact inbox title and body for a removed account, a banned account and an ordinary bad row, each in en-US and zh-CN. Another case runs a removed account, then a handle that fails at fanout_find_existing before the judge, then a blank, and gets the right notice for each, so no cause goes stale. email-templates renders both new bundles in both locales. Ablation (fix committed first at 29d9d08, trap restore, restore proven by HEAD blob hash and empty git diff HEAD): with the try-start clear made to self-assign, only the structural clear test went red, because the region-exit reset also empties the cause. With both resets self-assigning, the structural test and the engine stale-cause case went red. Restored: 56/56.",
      "gates": "Local, on 25cd82a, exit codes captured before any pipe: pnpm validate EXIT=0 (9 pre-existing field-no-consumers warnings, none on the diff); pnpm typecheck EXIT=0; pnpm test EXIT=0; pnpm build EXIT=0. No dispatch-gates.mjs in duly, so the gate list comes from package.json and AGENTS.md (the four gates). CI on PR #163: in_progress when this report was written, not waited on.",
      "dogfood_battery": "n/a: no ! in the PR title and no (narrowing) arm",
      "mcp_calls": "9: issue_read x3, pull_request_read x1, create_pull_request x1, issue_write(update assignee) x1, update_pull_request(reviewers, refused: author) x1, add_issue_comment x1 (this report), plus the PR body readback",
      "api_writes": "0 REST proxy writes. GitHub writes went through MCP as os-warren: PR create, PR assignee, this comment. Plus git push.",
      "open_questions": [
        { "question": "Conflict: os-dev rule 2 says a PR carries no assignees, but the order and the session harness both say assign os-warren. I assigned. Keep it?", "options": ["A keep the assignee", "B remove it"], "recommendation": "A, because the order explicitly asked and the reviewer request was refused (os-warren is the author), so the assignee is the only marker Warren sees" }
      ],
      "out_of_scope_findings": [
        "class: c · reach: objectstack validate EXIT=0 on a flow whose try_catch catch region has 3 sinks; the engine then refuses it at registration (WARN 'failed to register flow … region must be single-exit but has 3') and the flow never runs. Measured on this branch with the join edges removed. validateControlFlow lives in @objectstack/spec but validate does not reach it · repo: objectstack-ai/objectstack · dedupe words: validate, single-exit, region, try_catch, register flow",
        "carrier: none · noted, not filed: test/email-templates.test.ts #145 case 'delivers to a fr-FR recipient' read the delivery as in_flight once (three suites in parallel), then passed 4/4 alone. It reads delivery status without waiting for settle. Pre-existing, not touched."
      ]
    }

    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