Assignment fan-out — one piece of work becomes N independent tasks #6

Description

@os-warren

Assigning is the only write a manager makes in this product. Everything about this flow should protect that.

Files you own

  • src/flows/assignment.flow.ts (new), added to dulyFlows in src/flows/index.ts
  • test/assignment-fanout.test.ts (new)

The flow

type: 'record_change' on duly_assignment, firing when status transitions to 'dispatched'. Runs runAs: 'system' — the assignees are not the actor.

A loop node over record.assignees creating one duly_task per name:

Task fieldValue
subjectthe assignment's subject
ownerthe iterated assignee
business_unitthat assignee's unit
assignmentthe assignment record
source'assigned'
due_datethe assignment's due_date
visible_fromsame as due_date — an assignment has no lead time to spread
period_keyempty. An assignment has no period; the unique dispatch index does not apply to it
status'open'

Then, only ifrecord.needs_collection is true, create one further task owned by the assigner ("follow up once everyone is in"). A manager who assigns work must not automatically inherit a to-do list from having assigned it — that is the opt-in.

Why N tasks and not one shared task

Five names on one record produces something everybody can see and nobody owns, and it is the most reliable way a task tool starts being ignored. Five records, one owner each, each updated only by its owner. The manager's "3 of 5" is a rollup over the children (duly_assignment.task_count already exists as a Field.summary) — computed on read, maintained by nobody.

Idempotency

Re-saving a dispatched assignment must not fan out again. Guard on existing duly_task rows for the assignment. Adding a name to assignees on an already-dispatched assignment should create only the missing task — check per assignee, not per assignment.

Note the unique index on duly_task covers (duty, owner, period_key), which is all null/empty here, so it will not protect you. The guard has to be explicit.

Do not

  • create a status-rollup field the flow writes into
  • give the assigner a task unless needs_collection is set
  • set period_key to anything

Acceptance

  • dispatching to 5 assignees creates exactly 5 tasks, each with one owner and source: 'assigned'
  • task_count reads 5 without anything having written it
  • re-saving the dispatched assignment creates 0 more
  • adding a 6th assignee and re-dispatching creates exactly 1
  • needs_collection: false creates no assigner task; true creates exactly one
  • flow conditions use record.<field>; pnpm validate passes

Gates

pnpm validate && pnpm typecheck && pnpm test && pnpm build.

Activity

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

    , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
     blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
    }
    } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
    })();
    (function(){
    try {
    var __m = "github.com";
    var __re = new RegExp('^' + "github\\.com" + '
    
    Skip to content

    Assignment fan-out — one piece of work becomes N independent tasks #6

    Description

    @os-warren

    Assigning is the only write a manager makes in this product. Everything about this flow should protect that.

    Files you own

    • src/flows/assignment.flow.ts (new), added to dulyFlows in src/flows/index.ts
    • test/assignment-fanout.test.ts (new)

    The flow

    type: 'record_change' on duly_assignment, firing when status transitions to 'dispatched'. Runs runAs: 'system' — the assignees are not the actor.

    A loop node over record.assignees creating one duly_task per name:

    Task fieldValue
    subjectthe assignment's subject
    ownerthe iterated assignee
    business_unitthat assignee's unit
    assignmentthe assignment record
    source'assigned'
    due_datethe assignment's due_date
    visible_fromsame as due_date — an assignment has no lead time to spread
    period_keyempty. An assignment has no period; the unique dispatch index does not apply to it
    status'open'

    Then, only ifrecord.needs_collection is true, create one further task owned by the assigner ("follow up once everyone is in"). A manager who assigns work must not automatically inherit a to-do list from having assigned it — that is the opt-in.

    Why N tasks and not one shared task

    Five names on one record produces something everybody can see and nobody owns, and it is the most reliable way a task tool starts being ignored. Five records, one owner each, each updated only by its owner. The manager's "3 of 5" is a rollup over the children (duly_assignment.task_count already exists as a Field.summary) — computed on read, maintained by nobody.

    Idempotency

    Re-saving a dispatched assignment must not fan out again. Guard on existing duly_task rows for the assignment. Adding a name to assignees on an already-dispatched assignment should create only the missing task — check per assignee, not per assignment.

    Note the unique index on duly_task covers (duty, owner, period_key), which is all null/empty here, so it will not protect you. The guard has to be explicit.

    Do not

    • create a status-rollup field the flow writes into
    • give the assigner a task unless needs_collection is set
    • set period_key to anything

    Acceptance

    • dispatching to 5 assignees creates exactly 5 tasks, each with one owner and source: 'assigned'
    • task_count reads 5 without anything having written it
    • re-saving the dispatched assignment creates 0 more
    • adding a 6th assignee and re-dispatching creates exactly 1
    • needs_collection: false creates no assigner task; true creates exactly one
    • flow conditions use record.<field>; pnpm validate passes

    Gates

    pnpm validate && pnpm typecheck && pnpm test && pnpm build.

    Activity

    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

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
      Skip to content

      Assignment fan-out — one piece of work becomes N independent tasks #6

      Description

      @os-warren

      Assigning is the only write a manager makes in this product. Everything about this flow should protect that.

      Files you own

      • src/flows/assignment.flow.ts (new), added to dulyFlows in src/flows/index.ts
      • test/assignment-fanout.test.ts (new)

      The flow

      type: 'record_change' on duly_assignment, firing when status transitions to 'dispatched'. Runs runAs: 'system' — the assignees are not the actor.

      A loop node over record.assignees creating one duly_task per name:

      Task fieldValue
      subjectthe assignment's subject
      ownerthe iterated assignee
      business_unitthat assignee's unit
      assignmentthe assignment record
      source'assigned'
      due_datethe assignment's due_date
      visible_fromsame as due_date — an assignment has no lead time to spread
      period_keyempty. An assignment has no period; the unique dispatch index does not apply to it
      status'open'

      Then, only ifrecord.needs_collection is true, create one further task owned by the assigner ("follow up once everyone is in"). A manager who assigns work must not automatically inherit a to-do list from having assigned it — that is the opt-in.

      Why N tasks and not one shared task

      Five names on one record produces something everybody can see and nobody owns, and it is the most reliable way a task tool starts being ignored. Five records, one owner each, each updated only by its owner. The manager's "3 of 5" is a rollup over the children (duly_assignment.task_count already exists as a Field.summary) — computed on read, maintained by nobody.

      Idempotency

      Re-saving a dispatched assignment must not fan out again. Guard on existing duly_task rows for the assignment. Adding a name to assignees on an already-dispatched assignment should create only the missing task — check per assignee, not per assignment.

      Note the unique index on duly_task covers (duty, owner, period_key), which is all null/empty here, so it will not protect you. The guard has to be explicit.

      Do not

      • create a status-rollup field the flow writes into
      • give the assigner a task unless needs_collection is set
      • set period_key to anything

      Acceptance

      • dispatching to 5 assignees creates exactly 5 tasks, each with one owner and source: 'assigned'
      • task_count reads 5 without anything having written it
      • re-saving the dispatched assignment creates 0 more
      • adding a 6th assignee and re-dispatching creates exactly 1
      • needs_collection: false creates no assigner task; true creates exactly one
      • flow conditions use record.<field>; pnpm validate passes

      Gates

      pnpm validate && pnpm typecheck && pnpm test && pnpm build.

      Activity

      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

        , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
        Skip to content

        Assignment fan-out — one piece of work becomes N independent tasks #6

        Description

        @os-warren

        Assigning is the only write a manager makes in this product. Everything about this flow should protect that.

        Files you own

        • src/flows/assignment.flow.ts (new), added to dulyFlows in src/flows/index.ts
        • test/assignment-fanout.test.ts (new)

        The flow

        type: 'record_change' on duly_assignment, firing when status transitions to 'dispatched'. Runs runAs: 'system' — the assignees are not the actor.

        A loop node over record.assignees creating one duly_task per name:

        Task fieldValue
        subjectthe assignment's subject
        ownerthe iterated assignee
        business_unitthat assignee's unit
        assignmentthe assignment record
        source'assigned'
        due_datethe assignment's due_date
        visible_fromsame as due_date — an assignment has no lead time to spread
        period_keyempty. An assignment has no period; the unique dispatch index does not apply to it
        status'open'

        Then, only ifrecord.needs_collection is true, create one further task owned by the assigner ("follow up once everyone is in"). A manager who assigns work must not automatically inherit a to-do list from having assigned it — that is the opt-in.

        Why N tasks and not one shared task

        Five names on one record produces something everybody can see and nobody owns, and it is the most reliable way a task tool starts being ignored. Five records, one owner each, each updated only by its owner. The manager's "3 of 5" is a rollup over the children (duly_assignment.task_count already exists as a Field.summary) — computed on read, maintained by nobody.

        Idempotency

        Re-saving a dispatched assignment must not fan out again. Guard on existing duly_task rows for the assignment. Adding a name to assignees on an already-dispatched assignment should create only the missing task — check per assignee, not per assignment.

        Note the unique index on duly_task covers (duty, owner, period_key), which is all null/empty here, so it will not protect you. The guard has to be explicit.

        Do not

        • create a status-rollup field the flow writes into
        • give the assigner a task unless needs_collection is set
        • set period_key to anything

        Acceptance

        • dispatching to 5 assignees creates exactly 5 tasks, each with one owner and source: 'assigned'
        • task_count reads 5 without anything having written it
        • re-saving the dispatched assignment creates 0 more
        • adding a 6th assignee and re-dispatching creates exactly 1
        • needs_collection: false creates no assigner task; true creates exactly one
        • flow conditions use record.<field>; pnpm validate passes

        Gates

        pnpm validate && pnpm typecheck && pnpm test && pnpm build.

        Activity

        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

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
          Skip to content

          Assignment fan-out — one piece of work becomes N independent tasks #6

          Description

          @os-warren

          Assigning is the only write a manager makes in this product. Everything about this flow should protect that.

          Files you own

          • src/flows/assignment.flow.ts (new), added to dulyFlows in src/flows/index.ts
          • test/assignment-fanout.test.ts (new)

          The flow

          type: 'record_change' on duly_assignment, firing when status transitions to 'dispatched'. Runs runAs: 'system' — the assignees are not the actor.

          A loop node over record.assignees creating one duly_task per name:

          Task fieldValue
          subjectthe assignment's subject
          ownerthe iterated assignee
          business_unitthat assignee's unit
          assignmentthe assignment record
          source'assigned'
          due_datethe assignment's due_date
          visible_fromsame as due_date — an assignment has no lead time to spread
          period_keyempty. An assignment has no period; the unique dispatch index does not apply to it
          status'open'

          Then, only ifrecord.needs_collection is true, create one further task owned by the assigner ("follow up once everyone is in"). A manager who assigns work must not automatically inherit a to-do list from having assigned it — that is the opt-in.

          Why N tasks and not one shared task

          Five names on one record produces something everybody can see and nobody owns, and it is the most reliable way a task tool starts being ignored. Five records, one owner each, each updated only by its owner. The manager's "3 of 5" is a rollup over the children (duly_assignment.task_count already exists as a Field.summary) — computed on read, maintained by nobody.

          Idempotency

          Re-saving a dispatched assignment must not fan out again. Guard on existing duly_task rows for the assignment. Adding a name to assignees on an already-dispatched assignment should create only the missing task — check per assignee, not per assignment.

          Note the unique index on duly_task covers (duty, owner, period_key), which is all null/empty here, so it will not protect you. The guard has to be explicit.

          Do not

          • create a status-rollup field the flow writes into
          • give the assigner a task unless needs_collection is set
          • set period_key to anything

          Acceptance

          • dispatching to 5 assignees creates exactly 5 tasks, each with one owner and source: 'assigned'
          • task_count reads 5 without anything having written it
          • re-saving the dispatched assignment creates 0 more
          • adding a 6th assignee and re-dispatching creates exactly 1
          • needs_collection: false creates no assigner task; true creates exactly one
          • flow conditions use record.<field>; pnpm validate passes

          Gates

          pnpm validate && pnpm typecheck && pnpm test && pnpm build.

          Activity

          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

            , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
            Skip to content

            Assignment fan-out — one piece of work becomes N independent tasks #6

            Description

            @os-warren

            Assigning is the only write a manager makes in this product. Everything about this flow should protect that.

            Files you own

            • src/flows/assignment.flow.ts (new), added to dulyFlows in src/flows/index.ts
            • test/assignment-fanout.test.ts (new)

            The flow

            type: 'record_change' on duly_assignment, firing when status transitions to 'dispatched'. Runs runAs: 'system' — the assignees are not the actor.

            A loop node over record.assignees creating one duly_task per name:

            Task fieldValue
            subjectthe assignment's subject
            ownerthe iterated assignee
            business_unitthat assignee's unit
            assignmentthe assignment record
            source'assigned'
            due_datethe assignment's due_date
            visible_fromsame as due_date — an assignment has no lead time to spread
            period_keyempty. An assignment has no period; the unique dispatch index does not apply to it
            status'open'

            Then, only ifrecord.needs_collection is true, create one further task owned by the assigner ("follow up once everyone is in"). A manager who assigns work must not automatically inherit a to-do list from having assigned it — that is the opt-in.

            Why N tasks and not one shared task

            Five names on one record produces something everybody can see and nobody owns, and it is the most reliable way a task tool starts being ignored. Five records, one owner each, each updated only by its owner. The manager's "3 of 5" is a rollup over the children (duly_assignment.task_count already exists as a Field.summary) — computed on read, maintained by nobody.

            Idempotency

            Re-saving a dispatched assignment must not fan out again. Guard on existing duly_task rows for the assignment. Adding a name to assignees on an already-dispatched assignment should create only the missing task — check per assignee, not per assignment.

            Note the unique index on duly_task covers (duty, owner, period_key), which is all null/empty here, so it will not protect you. The guard has to be explicit.

            Do not

            • create a status-rollup field the flow writes into
            • give the assigner a task unless needs_collection is set
            • set period_key to anything

            Acceptance

            • dispatching to 5 assignees creates exactly 5 tasks, each with one owner and source: 'assigned'
            • task_count reads 5 without anything having written it
            • re-saving the dispatched assignment creates 0 more
            • adding a 6th assignee and re-dispatching creates exactly 1
            • needs_collection: false creates no assigner task; true creates exactly one
            • flow conditions use record.<field>; pnpm validate passes

            Gates

            pnpm validate && pnpm typecheck && pnpm test && pnpm build.

            Activity

            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

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              Assignment fan-out — one piece of work becomes N independent tasks #6

              Description

              @os-warren

              Assigning is the only write a manager makes in this product. Everything about this flow should protect that.

              Files you own

              • src/flows/assignment.flow.ts (new), added to dulyFlows in src/flows/index.ts
              • test/assignment-fanout.test.ts (new)

              The flow

              type: 'record_change' on duly_assignment, firing when status transitions to 'dispatched'. Runs runAs: 'system' — the assignees are not the actor.

              A loop node over record.assignees creating one duly_task per name:

              Task fieldValue
              subjectthe assignment's subject
              ownerthe iterated assignee
              business_unitthat assignee's unit
              assignmentthe assignment record
              source'assigned'
              due_datethe assignment's due_date
              visible_fromsame as due_date — an assignment has no lead time to spread
              period_keyempty. An assignment has no period; the unique dispatch index does not apply to it
              status'open'

              Then, only ifrecord.needs_collection is true, create one further task owned by the assigner ("follow up once everyone is in"). A manager who assigns work must not automatically inherit a to-do list from having assigned it — that is the opt-in.

              Why N tasks and not one shared task

              Five names on one record produces something everybody can see and nobody owns, and it is the most reliable way a task tool starts being ignored. Five records, one owner each, each updated only by its owner. The manager's "3 of 5" is a rollup over the children (duly_assignment.task_count already exists as a Field.summary) — computed on read, maintained by nobody.

              Idempotency

              Re-saving a dispatched assignment must not fan out again. Guard on existing duly_task rows for the assignment. Adding a name to assignees on an already-dispatched assignment should create only the missing task — check per assignee, not per assignment.

              Note the unique index on duly_task covers (duty, owner, period_key), which is all null/empty here, so it will not protect you. The guard has to be explicit.

              Do not

              • create a status-rollup field the flow writes into
              • give the assigner a task unless needs_collection is set
              • set period_key to anything

              Acceptance

              • dispatching to 5 assignees creates exactly 5 tasks, each with one owner and source: 'assigned'
              • task_count reads 5 without anything having written it
              • re-saving the dispatched assignment creates 0 more
              • adding a 6th assignee and re-dispatching creates exactly 1
              • needs_collection: false creates no assigner task; true creates exactly one
              • flow conditions use record.<field>; pnpm validate passes

              Gates

              pnpm validate && pnpm typecheck && pnpm test && pnpm build.

              Activity

              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

                , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                Skip to content

                Assignment fan-out — one piece of work becomes N independent tasks #6

                Description

                @os-warren

                Assigning is the only write a manager makes in this product. Everything about this flow should protect that.

                Files you own

                • src/flows/assignment.flow.ts (new), added to dulyFlows in src/flows/index.ts
                • test/assignment-fanout.test.ts (new)

                The flow

                type: 'record_change' on duly_assignment, firing when status transitions to 'dispatched'. Runs runAs: 'system' — the assignees are not the actor.

                A loop node over record.assignees creating one duly_task per name:

                Task fieldValue
                subjectthe assignment's subject
                ownerthe iterated assignee
                business_unitthat assignee's unit
                assignmentthe assignment record
                source'assigned'
                due_datethe assignment's due_date
                visible_fromsame as due_date — an assignment has no lead time to spread
                period_keyempty. An assignment has no period; the unique dispatch index does not apply to it
                status'open'

                Then, only ifrecord.needs_collection is true, create one further task owned by the assigner ("follow up once everyone is in"). A manager who assigns work must not automatically inherit a to-do list from having assigned it — that is the opt-in.

                Why N tasks and not one shared task

                Five names on one record produces something everybody can see and nobody owns, and it is the most reliable way a task tool starts being ignored. Five records, one owner each, each updated only by its owner. The manager's "3 of 5" is a rollup over the children (duly_assignment.task_count already exists as a Field.summary) — computed on read, maintained by nobody.

                Idempotency

                Re-saving a dispatched assignment must not fan out again. Guard on existing duly_task rows for the assignment. Adding a name to assignees on an already-dispatched assignment should create only the missing task — check per assignee, not per assignment.

                Note the unique index on duly_task covers (duty, owner, period_key), which is all null/empty here, so it will not protect you. The guard has to be explicit.

                Do not

                • create a status-rollup field the flow writes into
                • give the assigner a task unless needs_collection is set
                • set period_key to anything

                Acceptance

                • dispatching to 5 assignees creates exactly 5 tasks, each with one owner and source: 'assigned'
                • task_count reads 5 without anything having written it
                • re-saving the dispatched assignment creates 0 more
                • adding a 6th assignee and re-dispatching creates exactly 1
                • needs_collection: false creates no assigner task; true creates exactly one
                • flow conditions use record.<field>; pnpm validate passes

                Gates

                pnpm validate && pnpm typecheck && pnpm test && pnpm build.

                Activity

                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