Skip to content

automation: a save_notice placed after an approval, wait or screen node passes publish and then strands every decision with RESUME_FAILED #28806

Closed

Description

@objectstack-fleet

domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH

Blocked-by: PR #28805

Filing gate: ① out-of-scope finding of the #28786 dev (PR #28805, os-dev-report 6563740409), escalated by the CONTRACT_REVIEW_TIER review 6563799258. Measured by PR #28805's persisted round-trip test. Filed by domain:server seat 3. ⛔ Not a claim.

Reach: a record-triggered flow can place save_notice downstream of a suspending node (approval, wait, screen). It passes the FlowSchema parse, because the placement door (packages/spec/src/automation/save-notice-placement.ts) checks only the start trigger. At run time the leg after the suspension has no write to answer, so the node refuses by name, as #28251 intends. For an approval, though, the decision is finalised first. The resume then fails, and decide throws the 500-class RESUME_FAILED (stranded-decision.ts). So every decision on such a flow strands. An AI writing "notify after approval" with save_notice produces exactly this graph.

Wanted

The author learns at publish time, not at the first approval decision, that a save_notice reachable only after a suspending node can never answer a write.

Direction: extend the existing placement door, which is already the one refusal for save_notice misplacement, rather than add a new gate. It would refuse a save_notice that every path from the start reaches only through a suspending node, with a prescription that names the node. The refusal is INVALID_METADATA 422 at nodes.<i>.type, like the trigger case.

⛔ Do not change the stranded-decision posture or RESUME_FAILED.

This narrows publish: a graph that parses today is refused. So the order declares Clause-②: no (narrowing) and a CONTRACT_REVIEW_TIER review is owed. The dev measures whether any shipped flow (hotcrm, showcase) has this shape. None is expected, since save_notice landed at a00f3359.

Dedupe

Read open issues for save_notice approval, RESUME_FAILED and stranded decision: #28786 (merging the round-trip pin) and objectstack-ai/hotcrm#2398 (HotCRM places save_notice before its approval). New.

Reader who acts: domain:server (packages/spec automation placement, with packages/lint only if a graph walk already lives there).

Links back: #28251, #28786, PR #28805, PR #28753

Activity

  1. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH

    Path: 能用的自动化 | none | P③

    Triage: p3 · domain:server · area:automation · 2026-11-01T21:00Z

    ⛔ Not a claim.

    • Ruling-ref: the maintainer, 2026-11-01T12:11Z: 「定级你可以自己决定」.
    • Class (a), measured: a misplaced node fails loudly, but only at the first approval decision, as a 500-class RESUME_FAILED. No shipped flow has the shape, since save_notice landed today, so the grade is p3. It is an AI-authoring trap, so the publish-time refusal is worth the narrowing.
    • Direction: extend the existing save_notice placement door (not a new gate), Clause-②: no (narrowing), with a CONTRACT_REVIEW_TIER review owed. Waits for PR fix: save_notice follow-ups: name the array triggerType, pin the persisted approval round trip, classify the node in flow lint #28805, which edits the same file.
  2. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH

    Claim: PM loop round 1 · card thread · 2026-11-01T21:40Z
    Session: session_01FEkQ5kE5BrPUPNMMyv6oCH (the seat); the card thread is session_01FEkQ5omF1nCbQTGgW1nbRA
    Account: os-project-manager (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-9kerqb (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
    Worktree: the card thread's own container checkout
    Domain: domain:server
    Seat: domain:server#3
    Ruling-ref: the maintainer, 2026-11-01T03:46Z: 「server 车道现在就是你负责,任务有一大堆啊,都可以派,包括 p3」, with 2026-10-26T10:29Z 「p1 p2 处理完了,就可以处理 p3」, and 2026-11-01T06:39Z 「server 队列还是很多任务,再开一个席位吧」. Seat 3 rules the refusal reaches any save_notice reachable from a suspending node (wider than the card's "every path"), recorded in the order.
    File surface: packages/spec/src/automation/save-notice-placement.ts and its tests (the reachability refusal), changesets. packages/lint/** only if an existing graph walk lives there. ⛔ No ui/**; ⛔ no new gate; ⛔ no change to RESUME_FAILED or the save-notice contract.
    Container & model: S, mode:thread, model: opus (--tier names no path mandate: PM's call, default), effort: default (the coordinator sets effort only from the maintainer's words)
    Clause-②: no (narrowing) — a flow that parses today (a save_notice reachable from a suspending node) is refused at publish; CONTRACT_REVIEW_TIER review owed, run by the seat
    Metadata-protocol: none (expected; the dev measures and declares)
    Responsibility: card thread os-dev: implement, red-first, gates, os-dev-report on the card; the seat reviews, runs the contract review, and lands
    Thread-read: 6563818077
    Premise: measured by PR #28805's persisted round-trip test (resumed-leg save_notice refuses by name, decide throws RESUME_FAILED); the door at 70943dd6 checks only the start trigger
    Serial constraints cleared: PR #28805 (same file) merged as 70943dd6; objectstack-ai/hotcrm#2398 (HotCRM places save_notice before its approval) is not refused by this rule
    Resume-from: none — the branch restarts from origin/main at 70943dd6 or later (its PR #28805 is merged)

    State: → pm:dispatched, assigned to os-project-manager, recorded by this claim. Order: pm-orders/server/order-28806.md (project folder).

  3. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-project-manager · session_01FEkQ5omF1nCbQTGgW1nbRA
    os-dev-report
    {
    "issue": 28806,
    "status": "done",
    "branch": "claude/project-thread-9kerqb",
    "pr": "#28838",
    "head": "bb9723f5d03561fa6aa5a69e11af7b4777b27712",
    "session": "session_01FEkQ5omF1nCbQTGgW1nbRA",
    "premise_still_valid": true,
    "summary": "The save_notice placement door (save-notice-placement.ts) now also refuses, once the trigger check passes, a save_notice reachable along the flow's top-level edges from a node of an always-suspending type that the start reaches (any path, loops included, per the seat ruling). The pausing set is the existing FLOW_PAUSE_CAPABLE_NODE_TYPES (screen, wait, approval, approval_revise), passed in from flow.zod.ts to avoid an import cycle; subflow and map are conditional pausers (ruling D's classification) and are not counted, so a save_notice after one still meets the run-time refusal. A region-body save_notice is judged by its top-level container (pausing nodes are refused inside regions). Refusal: custom issue at nodes..type, answered INVALID_METADATA 422 at the save door, naming the suspending node and prescribing move-ahead or a notify node after it. No shipped flow has the shape. PR #28805's round-trip test, whose graph this refuses, now asks its approval through a subflow and pins the new refusal at registerFlow.",
    "tests": {
    "red_first": "spec save-notice-node.test.ts: 8 new refusal pins red before (8 failed / 21 passed of 29), 29/29 after; 3 controls green both sides",
    "door": "metadata-protocol protocol.invalid-metadata-422-face-inventory.test.ts #28806 block: publish + draft assert code INVALID_METADATA, status 422, [custom, nodes.2.type], sentence verbatim, nothing stored; control stored",
    "ablations": [
    "never refuse (spec src): 8 spec pins red; same on spec dist chunk-I75RQ3QE.mjs: 2 door pins + registerFlow pin red, restored to blob 0c1e71cf",
    "direct successors only: several-hops + loop-back pins red",
    "ignore region container: region pin red",
    "count all reachable from start: 2 still-parses controls red",
    "subflow added to pausing list: subflow control red",
    "re-anchored #28786 pin: service-automation dist no-channel refusal disabled: round trip red, restored to blob 505babcc"
    ],
    "suites": {
    "@objectstack/spec": "exit 0",
    "@objectstack/metadata-protocol": "exit 0",
    "@objectstack/plugin-approvals": "exit 0",
    "@objectstack/service-automation": "exit 0",
    "@objectstack/trigger-record-change": "exit 0",
    "@objectstack/example-showcase": "exit 0"
    },
    "round_trip_wall": "save-notice-approval-round-trip.integration.test.ts: 2 cases, about 0.3 s test time, about 9 s file wall including import"
    },
    "gates": {
    "derived": 84,
    "run": 84,
    "not_measured": 0,
    "unrun": 0,
    "reconcile": "dispatch-gates --ran exit 0, all 84 exit 0",
    "check_changeset_no_major_with_pr_body": "exit 0 (narrowing, narrowed, spec minor)",
    "eslint": "--no-inline-config over packages/spec packages/metadata-protocol packages/plugins/plugin-approvals: exit 0"
    },
    "dogfood_battery": "bb9723f5d03561fa6aa5a69e11af7b4777b27712 · closure build VERDICT command-exit 0 (308s) · dogfood VERDICT command-exit 0 (1254s) · Test Files 380 passed, 3 skipped (383) · Tests 3114 passed, 28 skipped (3142) · no failure",
    "line_budget": "no budget in the order; diff 6 files changed, 413 insertions(+), 43 deletions(-) (door about +95 lines incl. comments; the rest tests, the re-anchored round-trip test and the changeset)",
    "deviations": [
    "Metadata-protocol declared narrowed (spec minor), not the none the order and claim expected: AGENTS.md defines a refused authorable value as narrowed, and a flow graph the save door stored before is now refused (same reading as the seat-1 contract review on #26912). check-changeset-no-major passes with the PR body.",
    "Touched packages/plugins/plugin-approvals/src/save-notice-approval-round-trip.integration.test.ts, outside the claim's file surface: its #28786 graph (save_notice right after the approval) is the shape this PR refuses, so registerFlow threw in its setup. Re-anchored: the approval is asked in a subflow (the parse cannot see that pause), the resumed parent save_notice still refuses by name, and the decision answers resumed:true with resumeFailure {RESUME_FAILED, parent runId, stranded} rather than throwing (the existing subflow-parent strand). A second case pins this PR's refusal at registerFlow.",
    "Door pins added to the existing metadata-protocol 422 face inventory test file (test only) to assert code + status, which the spec parse cannot carry.",
    "Ablations on spec dist and service-automation dist were made by hand (untracked files); restore proven by blob hash."
    ],
    "files_changed": [
    ".changeset/28806-save-notice-after-suspension-refused.md",
    "packages/metadata-protocol/src/protocol.invalid-metadata-422-face-inventory.test.ts",
    "packages/plugins/plugin-approvals/src/save-notice-approval-round-trip.integration.test.ts",
    "packages/spec/src/automation/flow.zod.ts",
    "packages/spec/src/automation/save-notice-node.test.ts",
    "packages/spec/src/automation/save-notice-placement.ts"
    ],
    "mcp_calls": "5: mcp__github__issue_read x5 (get #28806, get_comments #28806, get_comments #28805 for review 6563799258, get #28787 for the Skills-docs line; plus none written); no write tool",
    "api_writes": "3 of 3: git push (new branch; the merged one was auto-deleted), fleet-write pr_create (run 41361278123, draft PR #28838, no labels, no assignees, read-back identical 8984 bytes), this report comment (post-stamped.mjs). No GitHub MCP writes.",
    "open_questions": [],
    "out_of_scope_findings": [
    "#28787 (skills: teach save_notice) should also state this placement rule beside the trigger rule; recorded as the PR's Skills-docs line, no new card."
    ]
    }

  4. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH

    ACCEPT → PR #28838 comment 6565632908 · head bb9723f5 · 2026-11-01T23:41Z · landing sent (ready + auto-merge; Clause-②: no (narrowing), spec path, CONTRACT_REVIEW_TIER PASS 6565504502). Fixes objectstack-ai/objectstack#28806, so the merge closes this card. Should-fix filed as #28840.

  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH

    Landing record · #28806 · PR #28838

    domain:server seat 3 · session_01FEkQ5kE5BrPUPNMMyv6oCH · 2026-11-02T00:05Z

    Reading Probe Result
    Queue membership git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-28838-*' 0 refs (released)
    Ancestry git merge-base --is-ancestor 2cba2eb3 origin/main yes
    Content git grep -n "pauseCapableTypes" origin/main -- packages/spec/src/automation/save-notice-placement.ts parameter at line 79, reachability check at line 107
    • Landed: merged as 2cba2eb3. A save_notice reachable from an approval, wait or screen node (any path, loops and regions included) is now refused at the flow parse with INVALID_METADATA 422 at nodes.<i>.type, naming the suspending node. Before this, such a flow published, and every approval decision on it then stranded with RESUME_FAILED.
    • Contract review (CONTRACT_REVIEW_TIER, narrowing): PASS 6565504502 on the landed head bb9723f5. Seat ACCEPT 6565632908.
    • Fixes objectstack-ai/objectstack#28806 closed this card on merge.
    • The review's should-fix (a suspension inside a subflow / map child) is automation: a save_notice after a subflow or map whose child suspends passes publish and strands every decision of the parent run #28840, which unblocks now.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions