Skip to content

service-automation: a built-in node's config value its own contract refuses still registers, then fails every run — the built-in half of #21848's class #21898

Description

@objectstack-fleet

This card takes the built-in node types. Parent #21848 keeps the plugin node types (its PR #21897 adds NodeExecutor.configContract and the approval executor declares it). Raised from #21848's build by domain:services seat 1 (#6021), session_011K3zqE8Pv1Evw5hc8tZCnN. The seat owns it with the parent's domain and priority, and dispatches it once PR #21897 lands.

Blocked-by: #15193

What is measured (#21848's dev, through the dogfood harness at 5fdd32ad):

  • POST /api/v1/automation with a create_record node whose config.outputVariable is 42 answers 200.
  • POST /api/v1/automation/:name/trigger then answers 400 FLOW_FAILED: "create_record mk: config does not satisfy the create_record contract — config.outputVariable: Invalid input: expected string, received number".

Mechanism, as the dev read it (verify before acting):

  • The built-in executors parse their Zod contract inside execute (packages/services/service-automation/src/builtin/parse-config.ts) and declare none at registration.
  • FlowSchema's config refusals check built-ins for presence only.
  • So a present built-in value that its contract refuses registers, then fails every run.

Why it is not mechanical: http parses after interpolation, loop parses conditionally, and the region containers' contracts contain their regions. So each built-in needs its own reading of what can be judged at registration.

Done when: each built-in node type whose config can be judged at registration declares its contract through the mechanism PR #21897 adds, a value its contract refuses is refused at registration with the located error, and each built-in that cannot be judged before run time is named, with the reason, in the PR.

Positions: packages/services/service-automation/src/builtin/ (the executors and parse-config.ts).


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 5, 2026

    @objectstack-fleet
    ContributorAuthor

    Re-read after the domain:spec seat's pointer 5997885807 · seat domain:services#1 (#6021) · session_011K3zqE8Pv1Evw5hc8tZCnN · 2026-10-05T16:46Z

    The built-in node contracts already live in the spec (getBuiltinNodeConfigContracts, 13 built-ins), and registerFlow parses FlowSchema first. So this card's fix is likely FlowSchema's own judge, judging built-in values where they can be judged at parse time, in the same shape #21850's PR #21893 gives the approval node. It is not a new executor-side contract. #21848 drops its executor-side judge for the same reason (the seat's note on #21848).

    This card stays pm:blocked, on #21848 and now also on #21850 / PR #21893. When those land, its domain is raised with triage, because a FlowSchema fix is spec-lane work.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 5, 2026

    @objectstack-fleet
    ContributorAuthor

    For triage: re-grade the domain · seat domain:services#1 (#6021) · session_011K3zqE8Pv1Evw5hc8tZCnN · 2026-10-05T23:44Z. ⛔ Not a claim.

    This card was pm:blocked on #21848, which has landed (PR #21897, 54fb60ac). The landed path changed where this card's fix belongs:

    Ask: re-grade this card's domain (the seat reads it as domain:spec) and its "Done when" against #21893's judge. The seat flips pm:blocked to pm:queue with pm:retriage in this act, so no seat dispatches it before triage answers.


    Generated by Claude Code

  3. added
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    and removed on Oct 5, 2026
  4. objectstack-fleet commented on Oct 5, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: ③ 验证:响亮拒绝错的,放行对的 — a flow node's config at the build doors | 缺项 (no item registers a built-in node whose value its own contract refuses) | P3

    Triage answers pm:retriage (6005685427): re-routed to domain:spec. "Done when" is re-read against #21893's judge. bug · priority:p2 · area:workflow · pm:queue are unchanged

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-05T23:52Z. ⛔ Not a claim, ⛔ not a dispatch.

    Triage: lands in packages/spec/src/automation/flow-node-config-refusals.ts (the judge that reads getBuiltinNodeConfigContracts) ⇒ domain:spec; rationale: the refusal belongs to FlowSchema's own judge, and widening what it refuses is a spec accept-set change.

    Verified on main (54fb60ac3f):

    Done when (replaces the card's):

    Priority is unchanged (p2): a refused value registers, then fails every run. Nothing blocks this card now that #21848 and #21850 have landed.

    Labels: domain:services → domain:spec, and pm:retriage is removed. The card's title still says service-automation:, and that prefix is historical: the landing is packages/spec.


    Generated by Claude Code

  5. 41 remaining items

  6. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: remain-behind landing (this card's PR #21974, on the maintainer's order 「你应该跟进到合并」; v18 opened 6037915987, unlock 6038095390) · 2026-10-07T13:01Z
    Session: session_01T9u38rswFp5Rw8DswRUReJ
    Account: os-project-manager (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-21898-builtin-node-config-values-judged (PR #21974 at f6981bd5d8; main comes in by a merge, ⛔ no rebase)
    Worktree: objectstack-issue-21898
    Domain: domain:spec
    Seat: domain:spec#1 (seat post #6017; the seat stays vacant, and this is its one remain-behind item, note 6030261850)
    File surface (at origin/main e67ba80049; stop on breach and explain in the report). It is exactly PR #21974's 15 files, the surface of claim 6011213547 as revised in 6012822762. Re-verified at this stamp: the 12 modified paths exist on main, and the 3 added paths do not. On main, only packages/spec/src/migrations/registry.ts has moved since the PR's base. That move is the pin bump's .objectui-sha citations (#22015), and the PR's own entry cites no pin.

  7. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 21898,
    "status": "blocked",
    "branch": "claude/issue-21898-builtin-node-config-values-judged",
    "pr": "#21974",
    "session": "session_01T9u38rswFp5Rw8DswRUReJ",
    "premise_still_valid": true,
    "head": "NOT PUSHED. The remote branch still reads f6981bd (ls-remote after the run). The merge commit 42ce99c exists only locally, on the shared repo's local branch of the same name.",
    "summary": "The merge of origin/main d4680d2 (#22063's merge commit) into f6981bd is clean and pure: HEAD^{tree} equals the 3-way merge-tree. The step-18 order is free, every generated artifact regenerates byte-identical, and the 95 derived gates, the spec, objectql, metadata-protocol and lint suites, the repo typecheck and ESLint are green. ONE test is red on the merged tree and green on origin/main alone. It is #22063's notify-template-slots.test.ts case refuses a value that is neither a string nor a template envelope at the contract parse, before anything is sent. Cause: this PR's judge now refuses title: 42 at FlowSchema.parse inside registerFlow, which is the PR's ruled behaviour. #22063's test was written for the run-only door, so its helper throws at registration before it ever executes. Per the dispatch I stopped. I edited no file in the PR's tree, did not push, and propose a test patch below that was proven in a throwaway worktree. The edit is content, so it needs a fresh contract-tier review, and it adds a 16th file to the claim's surface.",
    "merge": {
    "command": "git merge --no-ff --no-commit origin/main, then git commit --no-edit with the two model-free session trailers (Claude-Session, Co-authored-by: Claude). No rebase, no squash, no amend, no force.",
    "commit": "42ce99cf9152aef8868d69de7b91c7c44034aaee",
    "parents": "f6981bd5d8908ff420ada8917fc53591256c615c d4680d2",
    "message": "Merge remote-tracking branch 'origin/main' into claude/issue-21898-builtin-node-config-values-judged",
    "conflicts": "none. git: Auto-merging packages/spec/src/migrations/registry.ts / Automatic merge went well. No os-regen-pending marker was written, because none of the 15 PR files is routed to merge=os-regen (git check-attr merge).",
    "tree_HEAD": "8ea943fc6e8532bf206442c29da9ccede606138d (git rev-parse HEAD^{tree})",
    "tree_merge_tree": "8ea943fc6e8532bf206442c29da9ccede606138d (git merge-tree --write-tree origin/main f6981bd, origin/main = d4680d2). Equal. The staged index tree before the commit (git write-tree) was the same id."
    },
    "surface_check": "No file in the PR's surface differs from the 3-way merge result. For all 15 paths, the blob at HEAD equals the blob in merge-tree 8ea943fc6e. 14 of them are byte-identical to f6981bd. The 15th, packages/spec/src/migrations/registry.ts, is the only file both sides changed, and it auto-merged. Its +/- lines vs origin/main equal the PR's own +/- lines vs its old base c9761cd, and the lines the merge brought in equal main's c9761cd..d4680d2 lines (diff -U0 with line numbers stripped, both empty). The PR's delta vs main is unchanged: git diff --shortstat origin/main HEAD reads 15 files, 820 insertions, 65 deletions, the same as c9761cd2fb f6981bd5d8.",
    "step_18": {
    "command": "grep -oE 'order: [0-9]+' packages/spec/src/migrations/registry.ts | sort -t: -k2 -n | uniq -c | tail -3",
    "output": [
    " 1 order: 83",
    " 1 order: 84",
    " 1 order: 85"
    ],
    "reading": "Order 85 occurs once, and it is the entry id: 'flow-builtin-node-config-values-refused' (registry.ts:5599-5600). No other entry holds 85, and 84 is the next highest. No collision."
    },
    "regeneration": {
    "basis": "These are the commands the PR's report 6015122086 ran, all on the merged tree after a full turbo build of it.",
    "pnpm --filter @objectstack/spec check:generated": "exit 0: All 15 generated artifacts are up to date. It was measured against the dist the same pipeline's turbo build had just built from the merged tree.",
    "check:migration-registry": "exit 0: src/migrations/registry.ts is current (382 semantic, 247 retired-key, 218 retired-def)",
    "check:upgrade-guide": "exit 0: protocol-upgrade-guide.md is up to date.",
    "check:spec-changes": "exit 0: spec-changes.json is up to date.",
    "gen:migration-registry + gen:upgrade-guide + gen:spec-changes": "all exit 0 (wrote src/migrations/registry.ts (382 semantic, 247 retired-key, 218 retired-def), Wrote docs/protocol-upgrade-guide.md, Wrote packages/spec/spec-changes.json). Afterwards git status --porcelain is 0 lines and git diff HEAD is empty, so all three regenerate byte-identical and nothing was committed. gen:schema was not run, and check:generated --fix was not run."
    },
    "tests": {
    "turbo build": "pnpm exec turbo run build --filter=!@objectstack/docs --concurrency=2: VERDICT command-exit 0, Tasks 72 successful / 72 total, 0 cached, 7m35s.",
    "packages/spec (vitest --project local)": "exit 0. 622 files passed (622); 18567 passed, 1 todo (18568). The earlier rounds' 673 / 19431 counted both projects, local and repo. The dispatch named local.",
    "packages/services/service-automation": "exit 1. Files: 1 failed, 173 passed (174). Tests: 1 failed, 2115 passed (2116). The one red is described under red_on_merged_tree.",
    "packages/objectql": "exit 0. 379 files passed (379); 7513 passed (7513).",
    "packages/metadata-protocol": "exit 0. 221 files passed, 3 skipped (224); 28222 passed, 19 skipped (28241).",
    "packages/lint": "exit 0. 120 files passed (120); 5638 passed (5638).",
    "repo typecheck": "pnpm exec turbo run typecheck --concurrency=2: exit 0, Tasks 144 successful / 144 total. First run: 143 of 144, with @objectstack/dogfood red on TS2307 Cannot find module '@objectstack/setup' / '@objectstack/account'. That red was my sequencing, not the tree. I ran the bare pnpm install before the merge, and main's #21991 added those two devDependencies to dogfood. After pnpm install --frozen-lockfile both links exist and the re-run is 144 / 144 (143 cached), with pnpm --filter @objectstack/dogfood typecheck exit 0 on its own. The refresh changed only those two dogfood links, plus sharp 0.35.5 (next) and shell-quote 1.12.0 (launch-editor), which none of the suites above import.",
    "ESLint on the PR's 15 files": "pnpm exec eslint --no-inline-config --format json over the 15 paths: exit 0. 14 .ts files had 0 errors / 0 warnings. The 15th, the .changeset .md, is outside eslint's configuration (File ignored because no matching configuration was supplied, which counts as 1 warning). This is a per-file run, not a repo lint claim.",
    "derived gates": "node scripts/pm/dispatch-gates.mjs --commands derived 95 at 42ce99c: all 95 exit 0. --ran: 95 derived, 95 run, 0 NOT-MEASURED, 0 UNRUN. The set is one shorter than the PR's 96 because check:pm-widening-tells is no longer derived for this change set. That is a derivation change on main, not this PR.",
    "sha": "Every reading above was taken at 42ce99c (tree 8ea943fc6e). origin/main has since moved to 3d91885 (#22041: packages/lint and metadata-protocol, no file in the PR's surface). A re-merge would therefore owe the lint and metadata-protocol suites again."
    },
    "red_on_merged_tree": {
    "test": "packages/services/service-automation/src/builtin/notify-template-slots.test.ts, case refuses a value that is neither a string nor a template envelope at the contract parse, before anything is sent (#22063).",
    "merged_tree": "red, in both worktrees. In this one, the full suite has 1 failed. In the throwaway worktree at the same merge commit, with spec rebuilt there, the file is 1 failed / 3 passed. The error is a ZodError thrown from AutomationEngine.canonicalizeStoredFlow (src/engine.ts:4348, FlowSchema.parse(converted)) via registerFlow (engine.ts:4370), from the test helper deliveredFor (test:89). Its issue is code custom at nodes.1.config.title, with the message This notify node's config is refused at title by the notify contract: A template-typed slot accepts a bare template string or an envelope declaring dialect: 'template' only ....",
    "origin_main_alone": "green. In a throwaway worktree detached at d4680d2, after a fresh install and a build of service-automation's dependency closure (turbo 20/20), the same file is 4 passed (4).",
    "cause": "This PR's builtin value arm judges a present notify config value against NotifyConfigSchema at FlowSchema.parse. title: 42 carries no {token}, and notify parses before interpolation: PARSED_AFTER_INTERPOLATION stays http-only, and #22063's own executor comment still reads Parsed BEFORE interpolation. So the value is refused at registration, as ruling 6010677104 (A) intends. #22063's test asserts that refusal at execute time, through a helper that registers first, so it never reaches execute. The file's header says it was split from notify-node.test.ts because another in-flight change (this PR) edits that file. That split avoided the textual conflict but not the behavioural one.",
    "proposed_patch": "In the same shape this PR already gave notify-node.test.ts's refuses a node carrying BOTH template and inline title case. It asserts the registration refusal, then keeps the executor leg by registering a valid flow and putting title: 42 onto the stored node's config past the doors. Replace the case's first two lines (its it( title and const { result } = await deliveredFor({ title: 42 });) with:\n it('refuses a value that is neither a string nor a template envelope — at registration, and at the contract parse past the doors, before anything is sent', async () => {\n // #21898 — the flow parse judges a builtin node's present config value\n // against its executor contract, so registration refuses it at the key…\n expect(() => engine.registerFlow('notify_template_flow', notifyFlow({ recipients: ['user_1'], title: 42 })))\n .toThrow(/refused at title/);\n // …and the executor still refuses one that reaches it past the doors.\n const stored = engine.registerFlow('notify_template_flow', notifyFlow({ recipients: ['user_1'], title: TITLE }));\n const node = stored.nodes.find((n) => n.id === 'notify')!;\n node.config = { ...node.config, title: 42 };\n const result = await engine.execute('notify_template_flow', { params: PARAMS } as any);\nThe four existing expect lines stay as they are. The diff is 1 file, +10 / -2, saved at scratchpad issue-21898/m5/proposed3.patch (blob 674a8aa87b).",
    "proposed_patch_proof": "Run in a throwaway worktree detached at the merge commit 42ce99c, with spec rebuilt from that tree, and never committed or pushed. Unpatched: 1 failed / 3 passed. Patched: 4 passed (4). pnpm --filter @objectstack/service-automation typecheck exit 0 (tsc --noEmit plus check:test-typecheck: OK). ESLint on the patched file: 0 errors / 0 warnings. The anchor was proven on disk (old title 0 hits, new title 1 hit). The file was restored with git checkout HEAD --, and its hash equals the HEAD blob da7492f80f, with git diff HEAD at 0 lines. The worktree is removed.",
    "where_it_can_land": "Only together with this PR. On origin/main alone the patched case is red, because registration does not throw there. A door-agnostic variant (only the past-the-doors executor leg) could land on main first, but it would drop the registration assertion."
    },
    "interaction_22063": {
    "io-node-config.test.ts (spec, #22063)": "34 passed (34) on the merged tree, run by file and inside the full spec local run.",
    "notify-template-slots.test.ts (service-automation, #22063)": "3 passed, 1 failed on the merged tree. See red_on_merged_tree. 4 passed on origin/main alone.",
    "this PR's notify cases": "All green on the merged tree. In spec flow-builtin-node-config-values.test.ts (44/44 in the file): notify {severity:loud} is refused at severity, notify {recipients:[5]} is refused at recipients, notify {template:crm.deal_won} is refused at template. In flow-node-config-required.test.ts (58/58 in the file): the three notify cases, including a notify with neither title nor template is refused at title, in the notify contract's words. In service-automation, notify-node.test.ts is 16/16, including refuses a node carrying BOTH template and inline title, and config-parse.test.ts is 17/17, including refuses a missing required key (notify without title) and string slots parse RAW templates — a {token} recipients/title passes.",
    "joint behaviour no test pins": "Measured with FlowSchema.safeParse on each tree's built spec dist. On the merged tree, FlowSchema.parse now refuses six notify shapes at nodes.N.config.title or .message, in NotifyConfigSchema's own words: a blank bare title (empty or whitespace), a number, an envelope with a blank source, an envelope naming dialect cel, and a blank message. On origin/main alone all six are ACCEPTed. Both trees ACCEPT Hello, {record.name} and a tmpl envelope. This follows ruling A, since the executor refuses all six at every run. Corpus: git grep finds no blank title or message literal in examples/
    * or in non-test packages/**/src."
    },
    "mcp_calls": "0. No GitHub MCP call of any kind.",
    "api_writes": "1: POST /repos//issues/21898/comments (this os-dev-report, through scripts/pm/post-stamped.mjs and the fleet-write relay). No git push, because the dispatch says to stop on a red test. No PR edit, label or assignee write. Reads were REST GETs of comments on #21898, plus ls-remote and fetch.",
    "open_questions": [
    {
    "question": "How does the one-test patch land? It is content in a file outside the claim's 15-file surface, and it needs a fresh contract-tier review.",
    "options": [
    "A: PR #21974 takes the proposed patch as its 16th file. The claim surface is revised, a dev re-merges current main, commits the patch on top of the merge, and a fresh at-tier review is done on that head.",
    "B: a door-agnostic version (the executor leg only, with no registration assertion) lands on main first through its own PR and review. Then #21974 merges as a pure merge again.",
    "C: #21974's judge exempts notify title/message from the value arm, so #22063's test stays green unedited."
    ],
    "recommendation": "A. The edit belongs with the change that moves the door. It is the same pattern this PR already applied to notify-node.test.ts, it keeps #22063's executor assertion, and it adds the registration assertion that ruling A makes true. B splits one behaviour across two PRs and two reviews, and its main-only test would have to stay agnostic of a door that lands days later. C reopens ruling A for one node type and leaves a value the executor always refuses accepted at save."
    }
    ],
    "out_of_scope_findings": [
    "carrier: the PM / the next re-merge of PR #21974 · noted, not filed: origin/main moved during this run to 3d91885 (#22041: packages/lint/src/validate-expressions.ts and metadata-protocol), with no file in the PR's surface. The lint and metadata-protocol suites are owed again on the next merged head."
    ],
    "deviations": [
    "No push. Dispatch step 5 says to stop on a red test, so the remote head stays f6981bd. Because nothing was pushed, AGENTS' push-a-WIP-commit-before-long-steps rule was also not followed. The merge carries no authored content and is reproducible byte-exact from two remote shas (tree 8ea943fc6e), so no work was at risk.",
    "I ran the bare pnpm install before the merge, while §9 asks for a post-merge pnpm install --frozen-lockfile. That is what caused the dogfood TS2307. The refresh was run and the repo typecheck re-run green. The turbo build and the five suites ran before the refresh. The refresh changed only dogfood's two workspace links and two third-party versions (sharp under next, shell-quote under launch-editor), which none of those packages imports.",
    "The proposed patch was trial-applied in a throwaway worktree (objectstack-issue-21898-cmp), never in the PR's tree. It was restored by hash and the worktree removed. No file of the PR's tree was edited, and git status --porcelain there is 0 lines.",
    "The local branch claude/issue-21898-builtin-node-config-values-judged in the shared repo now points to the unpushed merge 42ce99c, not to f6981bd. I left it there so the commit stays reachable. The next dispatch's rule (use the local branch only if it equals f6981bd, otherwise go detached) covers it."
    ],
    "ci": "No new head was pushed, so no CI ran on a merge. The PR's CI at f6981bd is unchanged.",
    "cleanup": "The throwaway worktree /home/user/objectstack-issue-21898-cmp is removed. After this comment, /home/user/objectstack-issue-21898 has its node_modules removed and is then removed with git worktree remove. No background process is left running."
    }


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim revision (remain-behind landing) of claim 6038467957: same session, account, branch, worktree, domain and seat · 2026-10-07T14:27Z

    The dev's report 6040050529 stopped at step 5, as the claim asked it to.

    What it found:

    The disposition is the one revision 6012822762 already set for this family: each test keeps the subject its title names. Where the suite's harness reaches the executor without registerFlow, the test drives it that way. Otherwise, it also asserts the registration refusal with its location. This is not a new decision.

    • Option C, exempting notify title / message from the judge, would reopen ruling A. It is not taken.
    • Option B, landing the test change on main first, would be red on main alone. It is not taken either.

    Added to the file surface, one test file (⛔ no source line in any package moves):

    • domain:services, declared on [PM seat] domain:services — ⏳ vacant #6021 in this act: packages/services/service-automation/src/builtin/notify-template-slots.test.ts, the one case near :117.
      • It asserts that registerFlow refuses title: 42 with its location (refused at \title``).
      • Then it registers a valid flow, sets the stored node's config.title to 42, executes it, and keeps the case's four existing executor assertions.
      • Its title says both doors. No tracker id is added in a title, string or comment.
      • The dev proved it 4/4 on the merged tree and red on main alone, so it lands only with this PR.

    Also: main has moved to 3d9188502e (#22041, packages/lint and metadata-protocol), with no file in this surface. The dev merges current main again, so the lint and metadata-protocol suites re-run on the new head.

    Review: the new head changes something beyond a merge of main, so a fresh at-tier record is owed on it, as the claim stated. The seat commissions it after the push. Its scope is that one test file, plus the proof that everything else is a pure merge.

    Clause-②: yes (narrowing), unchanged.
    Thread-read: 6040050529

  9. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 21898,
    "status": "done",
    "branch": "claude/issue-21898-builtin-node-config-values-judged",
    "pr": "#21974",
    "session": "session_01T9u38rswFp5Rw8DswRUReJ",
    "premise_still_valid": true,
    "head": "c5545a54a61f33284db8ebddd8cb981a115cd56b (pushed. Remote branch and PR head both read c5545a5. The PR is still draft with auto_merge null, read back from REST.)",
    "summary": "Option A, per claim revision 6040112148 and cross-lane declaration 6040127154. Current origin/main 3d91885 was merged into the unpushed merge 42ce99c. The merge is clean and pure: its tree equals the 3-way merge-tree. The proven test patch went on top as one separate commit, touching only notify-template-slots.test.ts, the case at :117. The only change from proposed3 is that the #21898 — prefix is dropped from the code comment, so no tracker id appears in a title, string or comment. On the final head the new case is red without the test commit and green with it. Everything is green: service-automation is now fully green, along with spec, objectql, metadata-protocol, lint, the repo typecheck, the 95 derived gates, every regenerate-and-check, and ESLint over the 16 files. The push was refused with a GitHub-side 500 six times over about 9 minutes, then landed as an ordinary fast-forward on the 7th try.",
    "commits": {
    "head": "c5545a54a61f33284db8ebddd8cb981a115cd56b — test(service-automation): the notify title case asserts both doors — registration refuses, the executor refuses past the doors. One parent: 00bf19b.",
    "merge": "00bf19bdb56f4e67833d7776bfd0bd17707146b8 — Merge remote-tracking branch 'origin/main' into claude/issue-21898-builtin-node-config-values-judged. Parents: 42ce99c (the earlier merge of d4680d2 into f6981bd) and 3d91885 (origin/main).",
    "chain": "f6981bd5d8 (reviewed head), then 42ce99c (merge of d4680d2, tree 8ea943fc6e = its merge-tree), then 00bf19b (merge of 3d91885), then c5545a5 (the test commit). Both commits end with the model-free trailers Claude-Session and Co-authored-by: Claude. No rebase, amend, squash or force. The push was an ordinary fast-forward, f6981bd..c5545a5."
    },
    "purity": {
    "command": "git merge --no-ff --no-commit origin/main (3d91885) on 42ce99c, then git commit --no-edit with trailers",
    "conflicts": "none (Automatic merge went well). No os-regen-pending marker was written.",
    "tree_merge_commit_HEAD": "7e5776d0e8e3f8bd2e07240726bed90e9c26ae92 (git rev-parse 00bf19b^{tree}; the staged index tree before the commit was the same id)",
    "tree_merge_tree": "7e5776d0e8e3f8bd2e07240726bed90e9c26ae92 (git merge-tree --write-tree origin/main 42ce99c). Equal.",
    "tree_final_head": "2b73538e0ba4f6a2c2c6525f4adbe5c20605ef29 (c5545a5^{tree}, which is the merge tree plus the test commit)",
    "surface": "For all 15 earlier PR paths, the blob at the merge commit equals the blob in merge-tree 7e5776d0e8. All 15 except registry.ts are byte-identical to f6981bd, and registry.ts is byte-identical to the 42ce99c result (#22041 touched none of them). The 16th path, notify-template-slots.test.ts, equals main's blob da7492f80f at the merge, and only the test commit changes it. git diff --name-only 00bf19bdb5 HEAD names that one file. git diff --shortstat 3d9188502e 00bf19bdb5 reads 15 files, 820 insertions, 65 deletions, unchanged from before."
    },
    "pr_delta_vs_main": "16 files changed, 830 insertions(+), 67 deletions(-) (git diff --shortstat 3d91885 HEAD; merge base 3d91885). That is the earlier 15 files at 820 / 65, plus notify-template-slots.test.ts at 10 / 2.",
    "test_commit_diff": "diff --git a/packages/services/service-automation/src/builtin/notify-template-slots.test.ts b/packages/services/service-automation/src/builtin/notify-template-slots.test.ts\nindex da7492f80f..06ced6ffa1 100644\n--- a/packages/services/service-automation/src/builtin/notify-template-slots.test.ts\n+++ b/packages/services/service-automation/src/builtin/notify-template-slots.test.ts\n@@ -114,8 +114,16 @@ describe('notify — title / message are template slots', () => {\n expect(payload).toMatchObject(RENDERED);\n });\n \n- it('refuses a value that is neither a string nor a template envelope at the contract parse, before anything is sent', async () => {\n- const { result } = await deliveredFor({ title: 42 });\n+ it('refuses a value that is neither a string nor a template envelope — at registration, and at the contract parse past the doors, before anything is sent', async () => {\n+ // The flow parse judges a builtin node's present config value\n+ // against its executor contract, so registration refuses it at the key…\n+ expect(() => engine.registerFlow('notify_template_flow', notifyFlow({ recipients: ['user_1'], title: 42 })))\n+ .toThrow(/refused at title/);\n+ // …and the executor still refuses one that reaches it past the doors.\n+ const stored = engine.registerFlow('notify_template_flow', notifyFlow({ recipients: ['user_1'], title: TITLE }));\n+ const node = stored.nodes.find((n) => n.id === 'notify')!;\n+ node.config = { ...node.config, title: 42 };\n+ const result = await engine.execute('notify_template_flow', { params: PARAMS } as any);\n expect(result.success).toBe(false);\n expect(String(result.error)).toContain('does not satisfy the notify contract');\n expect(String(result.error)).toContain('config.title');\n",
    "test_commit_note": "This is proposed3 (proven earlier at 42ce99c: 4/4, typecheck, ESLint 0/0) with one line changed. // #21898 — the flow parse judges ... became // The flow parse judges .... Comparing the +/- lines with proposed3 gives exactly that one line. git grep '#21898' in the file finds 0.",
    "step_18": {
    "output": [
    " 1 order: 83",
    " 1 order: 84",
    " 1 order: 85"
    ],
    "reading": "Order 85 occurs once, on id: 'flow-builtin-node-config-values-refused' (registry.ts:5599-5600). 84 is the next highest. No collision."
    },
    "checks": {
    "install": "pnpm install --frozen-lockfile, run after the merge in a fresh worktree: exit 0. dogfood links @objectstack/account and @objectstack/setup are both present.",
    "turbo build": "pnpm exec turbo run build --filter=!@objectstack/docs --concurrency=2: VERDICT 0, 72/72 successful (22 cached), 3m39s.",
    "check:generated": "0: All 15 generated artifacts are up to date, against the dist the same run had just built.",
    "check:migration-registry": "0: registry.ts is current (382 semantic, 247 retired-key, 218 retired-def)",
    "check:upgrade-guide": "0: protocol-upgrade-guide.md is up to date.",
    "check:spec-changes": "0: spec-changes.json is up to date.",
    "byte-identical regeneration": "gen:migration-registry, gen:upgrade-guide and gen:spec-changes all exit 0 (wrote 382 / 247 / 218; wrote the guide; wrote spec-changes.json). Afterwards git status --porcelain is 0 lines and git diff HEAD is empty. Nothing was committed. gen:schema was not run, and check:generated --fix was not run.",
    "derived gates": "dispatch-gates --commands at c5545a5: 16 paths vs merge base 3d91885. The 95 gates derived are the same set as the earlier round, and all 95 exit 0. --ran: 95 derived, 95 run, 0 NOT-MEASURED, 0 UNRUN.",
    "spec --project local": "exit 0. 622/622 files; 18567 passed, 1 todo (18568).",
    "service-automation": "exit 0. 174/174 files; 2116/2116 tests. Fully green.",
    "objectql": "exit 0. 379/379 files; 7513/7513 tests.",
    "metadata-protocol": "exit 0. 221 files passed, 3 skipped (224); 28227 passed, 19 skipped (28246). This includes #22041.",
    "lint": "exit 0. 121/121 files; 5646/5646 tests. This includes #22041.",
    "repo typecheck": "pnpm exec turbo run typecheck --concurrency=2: exit 0. 144/144 successful (90 cached), 7m35s.",
    "ESLint, 16 files": "pnpm exec eslint --no-inline-config --format json over the 16 paths: exit 0. 15 .ts files had 0 errors / 0 warnings. The 16th, the .changeset .md, is outside eslint's configuration (File ignored because no matching configuration was supplied, which counts as 1 warning). This is a per-file run, not a repo lint claim.",
    "sha": "Every reading above was taken at c5545a5 (tree 2b73538e0b), in the final worktree, after the post-merge install. Nothing was committed after the readings: git status --porcelain is 0 lines at the end, and the pushed head is the measured head. origin/main still read 3d91885 at push time."
    },
    "red_green_proof": {
    "method": "On the final head, with the test commit committed. Mutation: git restore --source=HEAD^ -- notify-template-slots.test.ts (worktree only), so the file is the merge commit's version. Proven on disk: blob da7492f80f equals HEAD^'s, the new title has 0 hits and the old title has 1. Then the file was run. Restore: git checkout HEAD -- the path, by absolute path, under an EXIT/INT/TERM trap. Proven: blob 06ced6ffa1 equals HEAD's, git diff HEAD and git status --porcelain are 0 lines, the new title has 1 hit and the old title 0. Then the file was run again.",
    "without_test_commit": "red: exit 1. Files: 1 failed (1). Tests: 1 failed, 3 passed (4). The failure is refuses a value that is neither a string nor a template envelope at the contract parse, before anything is sent, a ZodError refused at title by the notify contract thrown by registerFlow.",
    "with_test_commit": "green: exit 0. Files: 1 passed (1). Tests: 4 passed (4), including refuses a value that is neither a string nor a template envelope — at registration, and at the contract parse past the doors, before anything is sent."
    },
    "push": "git push origin HEAD:claude/issue-21898-builtin-node-config-values-judged. 7 attempts. The first 6 (15:09:26Z to 15:14:59Z, by hand and then by backoff at 60s and 120s) were refused with remote: Internal Server Error and [remote rejected] (Internal Server Error), carrying GitHub request ids such as C03D:A1665:A1AD54:D6E418:6AC660A5. Under GIT_TRACE_CURL the receive-pack POST answered HTTP 200 with the error on the sideband, and the agent proxy reported no relay failure for github.com. The 7th, after 180s at 15:18:04Z, landed: f6981bd5d8..c5545a54a6. Reading: a transient server-side refusal, not a permission. While it was failing I suspected the merge's .github/workflows changes and a missing workflow permission, but the same push then succeeded, which rules that out.",
    "mcp_calls": "0",
    "api_writes": "1 REST write: POST /repos//issues/21898/comments (this os-dev-report, via post-stamped and the fleet-write relay). Plus 1 git push that landed. The 6 refused attempts wrote nothing. No PR edit, ready flip, auto-merge or label/assignee write. Reads: REST GETs of comments 6040112148 and 6040127154 and of pulls/21974; ls-remote; fetch.",
    "open_questions": [],
    "out_of_scope_findings": [],
    "deviations": [
    "The push came after the checks, as dispatched, rather than as a WIP push before the long steps. Both commits were local in the shared object store throughout, and the measured head is the pushed head.",
    "The push needed 7 attempts because of GitHub-side 500s. The retries were one backoff script (60/120/180s, stopping on the first success), run in the background and waited on in the foreground. It has exited."
    ],
    "review": "Per revision 6040112148, a fresh at-tier record is owed on c5545a5. Its scope is the test commit's one file, plus the purity proof above for everything else.",
    "ci": "in_progress at report time on c5545a5. Not awaited, per contract.",
    "cleanup": "After this comment, /home/user/objectstack-issue-21898 has its node_modules removed and is then removed with git worktree remove. No background process is left. The shared repo's local branch reads c5545a5, the same as the remote."
    }


    Generated by Claude Code

  10. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    ContributorAuthor

    Contract review

    2026-10-07T15:59Z

    Served-tier: CONTRACT_REVIEW_TIER
    Head-sha: c5545a54a61f33284db8ebddd8cb981a115cd56b
    Local-runs: probe — the pair's joint behaviour (the judge over the merged NotifyConfigSchema) is pinned by no committed test and the dispatch asked for it measured, so one private partial clone in this act's scratch directory (⛔ never the shared checkout) was built once (pnpm install --frozen-lockfile, OS_SKIP_DTS=1 turbo run build --filter='@objectstack/service-automation...', 21 tasks) and used for one vitest pass: a scratch FlowSchema / flowNodeConfigRefusals file (19 cases, deleted after, never committed), notify-template-slots.test.ts at the head and with the file restored to HEAD^ then put back, and the pair's pin files; nothing was committed or pushed, and no derived gate family was re-run. The purity readings are git plumbing on fetched objects in that clone (merge-tree --write-tree, blob ids, diff -U0), taken before the install registered the os-regen driver there; none of the 16 paths is routed to it (git check-attr merge).

    Inputs: card #21898, its body and all 26 comments, the ones that matter read in full (ruling A 6010677104; claim 6011213547 and its revision 6012822762; the records 6014379957 FAIL and 6015330660 PASS on f6981bd5d8; ACCEPT 6015366099; triage 6038095390; landing claim 6038467957 and its revision 6040112148; dev reports 6040050529 and 6040936454); PR #21974, its body and 16-file list; PR #22063 (#22054), its body, 7-file list, merge commit d4680d2820 and its contract review 6033083158; the sources at c5545a54a6 named below; the check-runs on the head, polled at most once a minute until complete. Both legs: the path leg (automation/flow-node-config-refusals.ts, flow.zod.ts, migrations/**) and Clause-② yes (narrowing).

    ① Derived judgments

    1. Purity of both merges (RIGHT), re-derived. 42ce99cf91 has parents f6981bd5d8 and d4680d2820 (merge base c9761cd2fb); git merge-tree --write-tree d4680d2820 f6981bd5d8 answers 8ea943fc6e8532bf206442c29da9ccede606138d, which is 42ce99cf91^{tree} (the reverse operand order gives the same id). 00bf19bdb5 has parents 42ce99cf91 and 3d9188502e (merge base d4680d2820); git merge-tree --write-tree 3d9188502e 42ce99cf91 answers 7e5776d0e8e3f8bd2e07240726bed90e9c26ae92, which is 00bf19bdb5^{tree}. Both equal the seat's readings. c5545a54a6 has the one parent 00bf19bdb5, tree 2b73538e0b, and git diff --name-only 00bf19bdb5 c5545a54a6 names only notify-template-slots.test.ts. The delta against the merge base 3d9188502e is 16 files, +830 / −67: the earlier 15 at +820 / −65, the same figures as c9761cd2fb..f6981bd5d8, plus the test file at +10 / −2.

    2. The 15 earlier files carry no authored change beyond the merges (RIGHT). For 14 of them the blob id is identical at f6981bd5d8, 42ce99cf91, 00bf19bdb5 and c5545a54a6. The 15th, packages/spec/src/migrations/registry.ts, moved once, at 42ce99cf91 (a9299a5ab2 → 50ef39e36a), and never after: its added and removed line set against 3d9188502e (96 lines) is byte-identical to the PR's own line set against c9761cd2fb at f6981bd5d8, and the line set the merges brought in (f6981bd5d8..c5545a54a6, 44 lines) is byte-identical to main's own c9761cd2fb..3d9188502e lines on that file: all of them chore(objectui): bump the console pin to a58626c88dc8 (carries objectui#11670 and #11669) #22015's .objectui-sha citation strings, nothing else. Order 85 occurs once at the head, on flow-builtin-node-config-values-refused; 84 is the next highest.

    3. The 16th file is main's until the test commit (RIGHT). notify-template-slots.test.ts is absent at f6981bd5d8, equals main's blob da7492f80f at both merge commits, and only c5545a54a6 changes it, to 06ced6ffa1.

    4. The test commit follows revision 6012822762's disposition (RIGHT). The suite's only harness, deliveredFor, registers and then executes, so the executor's parse cannot be reached without registerFlow and without a source change: the disposition's "otherwise" branch applies. The case keeps the subject its title names, the contract parse before anything is sent: the executor leg still asserts success === false, does not satisfy the notify contract, config.title, and messaging.emitted empty. It adds the registration refusal with its location: registerFlow(…title: 42) throws, matched on refused at `title` , the key the judge anchors at. The title now names both doors. It is the shape this PR already gave notify-node.test.ts's both-template-and-title case and config-parse.test.ts's runPatched.

    5. The mutate-the-stored-node technique is sound and hides nothing (RIGHT). registerFlow returns the very object it stores (engine.ts: this.flows.set(name, parsed) then return parsed), and execute reads this.flows.get(flowName), so writing title: 42 onto the stored node's config is exactly a value past the doors reaching the executor's parseNodeConfig, which parses node.config as authored before it interpolates (notify-node.ts: "Parsed BEFORE interpolation", then cfg.title?.source). The first registerFlow throws inside canonicalizeStoredFlow at FlowSchema.parse, before any engine state is written, so the second registration under the same name is clean; engine is built in beforeEach, so the mutation reaches no other case. Measured: at the head the file is 4 / 4 (37 / 37 across the three service-automation files of the pair); with the file restored to HEAD^, main's blob, the case is red with the ZodError registerFlow throws at nodes.1.config.title, 1 failed / 3 passed; the file was restored to the head blob afterwards.

    6. No tracker id (RIGHT). The commit's added lines carry none in the title, in a string or in the two comment lines (the #21898 — prefix proposed in 6040050529 was dropped, as 6040936454 says). The commit message ends on the model-free trailer pair.

    7. The pair, what is newly refused (RIGHT: ruling A's behaviour, inside Clause-② yes (narrowing)). NotifyConfigSchema at the head types title and message with TemplateExpressionInputSchema (string arm: non-blank, transformed to the envelope; envelope arm: the dialect: 'template' literal; one invalid_union at the key) plus the notify-only rule, a custom issue at the key for an envelope with no non-blank source. The executor parses node.config as authored (notify is not in PARSED_AFTER_INTERPOLATION), so each of those refusals fails every run: ruling A's class. The judge keeps each of them: title and message sit on no ledger predicate or value slot (FLOW_NODE_EXPRESSION_PATHS has no notify row), are no region slot and no run-resolved key, and a token-free value is judged. Measured with FlowSchema.safeParse on the head's src: a blank title, a whitespace title, title: 42, { dialect: 'template', source: ' ' }, { dialect: 'template', ast: … } with no source, { dialect: 'cel', source }, message: '' and message: 42 are each refused with exactly one issue, code custom, path nodes.1.config.title or nodes.1.config.message, message opening This `notify` node's config is refused at `title` by the notify contract: (or message) followed by the contract's own sentence; flowNodeConfigRefusals('notify', …) answers node-config-refused-by-contract at the key. Same arm, same code, same anchoring as the PASS record judged; nothing newly accepted, so the declared arm holds.

    8. Nothing the new notify schema accepts is refused, and a template value is never refused for its pre-interpolation type (RIGHT). Measured on the same tree: 'Hello', '{record.name}', '{{record.name}}', '${record.name}', a tmpl envelope with a token and one without, a bare and a tmpl message, and the template path each pass FlowSchema.safeParse with no refusal from the judge. The hold-back, carriesInterpolationToken, walks the strings inside objects and arrays, so a token inside an envelope's source is held back exactly as a bare string's is, and a token-free envelope is judged and accepted by the contract.

    9. No transformed output leaks into the stored flow (RIGHT). The judge's contract interface is structural safeParse only; flowNodeConfigRefusals reads result.error.issues and never .data; it runs inside FlowSchema's superRefine, which can only add issues; FlowNodeSchema.config stays z.record(z.string(), z.unknown()), and the one place a node's config is written back from a contract's parse is parseEndNodeConfig, for end alone. Outside tests, getBuiltinNodeConfigContracts() has one consumer across packages/spec, lint, service-automation, cli, metadata-protocol and objectql: the judge. Measured: FlowSchema.parse and defineFlow return config.title as the authored string, and an authored envelope byte-for-byte as authored.

    10. The PR's prose against the new main (RIGHT: no sentence false). Read for every mention of notify, title, message, string and template: the changeset (the notify template-beside-title rule under "What is refused" and FROM → TO row 7), the D3 entry (surface: notify severity loud, a notify template beside an inline title; replacement: keep template or the inline title / message, not both; acceptanceCriteria: a token "runs … where the slot takes a string", and a bare notify title still takes a string), the regenerated order-85 region (identical to the entry after whitespace normalisation; no notify sentence of its own), the judge's docblocks (a notify with no template needs title, a rule feat(spec): notify title/message are template slots — bare string or tmpl envelope #22063 left unchanged), flow.zod.ts's two moved blocks, and the PR body ("every other builtin is judged on every present value: … notify …"; the pins at severity and the notify rule; "refused at … template"). Each is still true. feat(spec): notify title/message are template slots — bare string or tmpl envelope #22063's own changeset on main names no door this PR contradicts. The kit's example lists predate feat(spec): notify title/message are template slots — bare string or tmpl envelope #22063 and do not enumerate the notify template-slot class (blank, non-string and non-envelope, foreign dialect, blank-source envelope); the general sentence, "a value its executor contract refuses", covers it, and feat(spec): notify title/message are template slots — bare string or tmpl envelope #22063's changeset carries that class. Not a finding.

    11. CI on c5545a54a6 (RIGHT: complete, green). 35 check-runs, polled once a minute until none was running: 32 success, 3 skipped, 0 failed, 0 cancelled, no runner loss, so no log had to be read. The three skips are the roster's expected ones, Build Docs, Console Pin Gate and Packed-tarball smoke (opt-in). The seven required contexts all concluded success: Lint & Repo Gates (which carries check:migration-registry and check:adr-0087-registration), TypeScript Type Check (with Type Check · source gates, Type Check · consumer gates, Type Check · workspace and Type Check · debt ledger), Test Core (the aggregate and its six shards), Dogfood Regression Gate (the aggregate and its three shards), Build Core, Temporal Conformance (live PG + MySQL) and Governed Surface Queue Guard. Check Changeset, Check PR Size, Spec property liveness, Dogfood Verify CLI, Check Documentation Links, Flag docs affected by code changes, Auto Label, filter and the four PR-automation claim rows are success too.

    ② Semver level

    Level (RIGHT). .changeset/21898-flow-builtin-node-config-values-refused.md: '@objectstack/spec': minor, Clause-②: yes (narrowing), exactly one marker in the HTML-comment form the gate reads, adr-0087: registered flow-builtin-node-config-values-refused, and the **BREAKING** banner. Triage 6038095390: a breaking change landing before the opening card is minor with its banner and disposition under the launch-window convention, and major opens only once pre mode is in. .changeset/pre.json is absent on main at 3d9188502e and at its tip b04a5295f7, so pre mode is not entered. The Clause-②: line on the PR and in the changeset still matches what the diff publishes with #22063 merged: at the build doors nothing is newly accepted, and the notify values newly refused there are values the executor refuses at every run. Check Changeset is green on the head.

    ③ Boundary flags

    • The PR body was not re-patched for the landing round. Its "Cross-lane fixtures re-judged" section names the five patch-round files and not notify-template-slots.test.ts, and its merge-gate paragraph reads "At this base the pin is 0abd4f9f87", a base that has moved (main carries a58626c88d, at or after 5ba255538a; the gate is met). No sentence about notify is false; the 16th file and its conversion are named in the commit title, in 6040112148 and in 6040936454. The owning seat can patch the body in one paragraph; that moves no byte of the head and needs no new record. Advisory.
    • The refusal prose a blank, numeric or foreign-dialect notify title or message now receives at the build doors prescribes '{{record.name}}', the shared TYPED_EXPRESSION_* text that 6033083158 ③ flag 3 escalated on expression.zod.ts (this slot's renderer reads {token}). This PR surfaces it one door earlier, in the contract's own words as ruling A requires; the carrier is that escalation, not this PR.
    • main has moved to b04a5295f7 (ci: extend the turbo remote cache to the Type Check, Test Core, dogfood and Temporal build steps #22086: two CI workflow files, no PR path; step-18 order 85 still free; no pre.json); git merge-tree of the head onto it is clean. Nothing beyond main itself is needed, so by 6038467957's rule no further record is owed for that merge; the queue rebuilds on it.
    • open_questions: none in 6040936454. Governed surfaces: none of the 16 paths. 897 changed lines, under the human-merge threshold. The PR is a draft on base main with auto-merge unarmed.

    Verdict

    Both merges are pure to the tree id; the 15 earlier files are byte-identical to the reviewed head except registry.ts, whose lines are the PR's own plus main's pin citations; the one test commit follows the family's disposition, keeps its subject, adds the registration leg with its location and carries no tracker id; the pair with #22063 refuses at the build doors exactly what the notify executor refuses at every run and accepts every value the new schema accepts, template values held back; no transformed output leaks; the kit's prose stays true; the grade stays minor with the banner and the marker; CI is complete and green. No item needs fixing.

    Implemented-by: claude/issue-21898-builtin-node-config-values-judged
    Reviewed-by: session_01T9u38rswFp5Rw8DswRUReJ

    VERDICT: PASS

  11. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #21974 at c5545a54a6 (the landing head: main merged in, plus one test commit)

    domain:spec seat 1 · os-project-manager · session session_01T9u38rswFp5Rw8DswRUReJ · 2026-10-07T16:08Z · the review of record for the landing head. This is the remain-behind landing (claim 6038467957, revised 6040112148). It stands on ACCEPT 6015366099 at f6981bd5d8, which still holds for the 15 files it read.

    What the seat re-read on GitHub and in git, not from the reports:

    • Chain: f6981bd5d8, then 42ce99cf91 (a merge of d4680d2820), then 00bf19bdb5 (a merge of 3d9188502e), then c5545a54a6 (the test commit).
      • Each merge's tree equals git merge-tree --write-tree of its parents: 8ea943fc6e and 7e5776d0e8.
      • 00bf19bdb5..c5545a54a6 names one file, +10 / −2.
      • The delta against main is 16 files, +830 / −67.
      • Both commits carry the model-free trailers.
    • The test commit: notify-template-slots.test.ts, the one case near :117, read in full.
      • It asserts that registerFlow refuses title: 42 with refused at \title`. Then it sets a registered node's config.titleto42`, executes, and keeps the case's four executor assertions.
      • It follows revision 6012822762's disposition. It adds no tracker id in a title, string or comment.
    • Contract review: at-tier PASS 6041691539 on c5545a54a6, both legs.
      • The purity proof was re-derived in a private clone.
      • It confirms that the pair with feat(spec): notify title/message are template slots — bare string or tmpl envelope #22063 refuses only what ruling A refuses, and that nothing the new notify schema accepts is refused. That includes {token} strings and tmpl envelopes.
      • No transformed output leaks into the stored flow.
      • No sentence in the PR's prose is now false.
      • The grade is right: minor, BREAKING, the ADR-0087 marker. Pre mode is not entered.
    • Dev report 6040936454:
      • The new case is red without the test commit and green with it.
      • service-automation is 174 / 174 files and 2116 / 2116 tests. Spec, objectql, metadata-protocol, lint, the typecheck, the 95 derived gates and the byte-identical regeneration are all green.
      • ESLint is 0 / 0 on the 16 files.
    • Step 18: order 85 occurs once on the head. On main at bafb58bb08, 84 is still the highest.
    • Not governed: check-governed-merges reads 0 of 16 paths, with 897 changed lines.
    • CI on c5545a54a6: complete, with 32 success and 3 skipped. check-expected-skips reads all three as in its roster, and 0 failed.
    • Merge: git merge-tree onto origin/main bafb58bb08 is clean. No commit on main since 3d9188502e touches any of the 16 paths, and no merge-queue ref is open.
    • Gates: the pin clause of ruling 6010677104 is met. The v18 gate ([gate] the v18 development line is not open — ADR-0131 execution cards are blocked on this card #15193) has closed. ⛔ chore: version packages #21988 is not merged.

    PR body: a landing note was added under its merge-gate paragraph. It names the 16th file, the two merges and this record. Fixes #21898 is unchanged.

    Carried, not filed (as in 6015366099):

    • the PARSED_AFTER_INTERPOLATION / RUN_RESOLVED_KEYS reconciler row (domain:services);
    • the flows.mdx advisory.

    The review notes a third advisory: the shared '{{record.name}}' prescription now surfaces at the build doors. It was already flagged in #22063's review (6033083158 ③).

    Landing: the seat flips the PR ready and arms auto-merge through the relay right after this record. Fixes #21898 closes the card on merge.

  12. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #21974 → ac9f8bd471. The card is closed completed

    domain:spec seat 1 · os-project-manager · session session_01T9u38rswFp5Rw8DswRUReJ · 2026-10-07T16:49Z · holder of claim 6038467957 (revised 6040112148), which this act releases. It also ends this session's remain-behind (6030261850).

    • Landed: PR feat(spec)!: the build doors refuse a builtin node config value its executor contract refuses, with its location #21974 merged through the merge queue at 2026-10-07T16:48Z as ac9f8bd471. It has one parent (bafb58bb08) and is an ancestor of origin/main.
    • Content check: all 16 files on origin/main are blob-equal to the reviewed head c5545a54a6 (at-tier PASS 6041691539, ACCEPT 6041850001). Step 18 on main holds order 85 once, on flow-builtin-node-config-values-refused.
    • What now holds (ruling A, 6010677104): a builtin flow node's present config value that its executor contract refuses is refused at the build doors, with its location: FlowSchema.parse, objectstack validate and objectstack compile. Today that set is ruling A's measured pair (create_record outputVariable: 42, a screen field min: '1') and the like. Since feat(spec): notify title/message are template slots — bare string or tmpl envelope #22063 it also includes a blank, whitespace or numeric notify title / message. Template and interpolation values are never refused for their pre-interpolation type.
    • The release state: it ships as @objectstack/spec minor with the BREAKING banner and the ADR-0087 marker, on the v18 line, before the pre-mode opening card. ⛔ No release act was taken.

    Carried, not filed (from ACCEPT 6015366099):

    • the PARSED_AFTER_INTERPOLATION / RUN_RESOLVED_KEYS reconciler row (domain:services);
    • the flows.mdx advisory.

    Next: #21982, the family's key half, is unblocked in this round. It was Blocked-by: #21898 and returns to pm:queue.

    This act removes pm:dispatched and the assignee.

  13. added 3 commits that reference this issue on Oct 9, 2026
    ac9f8bd
    78f841b
    2f70c22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:workflowApprovals and automation — the work that runs without a person driving itbugSomething isn't workingdomain:specpriority:p2Medium: important, M3target:v18

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions