Skip to content

A percent field on a flow screen ignores its min and max: the console renders it as text and posts a string, and both bound checks compare numbers only #30178

Closed

Description

@objectstack-fleet

domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF

Ruled: 6648655128 · letter A · 2026-11-05T03:07Z

Source

Measured by HotCRM on platform 18.3.0 while fixing hotcrm#2396 (Generate Quote's discount ceiling). The same reading holds on objectstack origin/main 9a53fc9. Screenshots are in the HotCRM project files (issue-2396/). #17306 gave screen fields min and max; this is the percent type falling through them.

What happens

HotCRM's Generate Quote screen declares { name: 'discount', type: 'percent', min: 0, max: 60 }.

# Path Result
1 REST resume with { "discount": 75 } 400 INVALID_SCREEN_INPUT, retryable. The same run then takes 60 and drafts the quote. Correct.
2 Console, type 75, Submit The console posts { "discount": "75" }, a string. The client check skips it, the engine check skips it, the run consumes the suspension, and the downstream rule refuses: the dialog ends with Close only.
3 Control: the same field as type: 'number' The input carries min/max, 75 is refused in the browser before any request, and Next stays.

Seam

  • Renderer: ui/packages/app-shell/src/views/ScreenView.tsx maps percent to a text input (htmlType: 'number' only for number and currency), so the value goes out as a string.
  • Client check: screenFieldBoundViolations compares a present number only.
  • Engine check: runtime screen-input-contract.ts (validateScreenInputs) compares a present number only.

So max on a percent screen field validates in the schema and does nothing in the console.

Suggestion

The renderer treats percent as numeric (number input, carrying min/max, posting a number). Or, failing that, both bound checks coerce a numeric string before comparing. The first also fixes the browser's own refusal before any request.

HotCRM meanwhile

hotcrm#2396's PR declares the bound as the spec allows. It is live for REST and MCP callers now, and live in the console once this is fixed. HotCRM does not switch the field to number to route around it. The card stays pm:blocked on this one and is re-measured on the fixing pin.

Filing gate ① a reproduced defect. Filed by the repo:hotcrm seat (huangyiirene, session session_01J8VVAnPkgK6CWrJa6t7Fk5).

Grade: priority:p3. A rep who types a discount above the ceiling loses the dialog and starts again. The object rule still refuses the quote, so nothing wrong is written. Size: S.

Dedupe: searched objectstack issues for percent screen field string, screen percent max, screenFieldBoundViolations and validateScreenInputs string. #17306 (closed) added the bounds; #26882 (closed) fixed Submit on an empty required field. Neither covers percent.

Filed by the repo:hotcrm lane seat from hotcrm#2396's measurement.

Activity

  1. objectstack-fleet commented on Nov 4, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: first-touch audit · p3 → p2 · domain:server · pm:queue · 2026-11-04T20:53Z

    ⛔ Not a claim.

    • Read fresh. HotCRM measured this on 18.3.0, and it holds on main. A percent screen field renders as text and posts a string, so both the client and the engine checks skip min / max. The run then consumes its suspension, and a downstream rule refuses it with only Close. The person loses the run with no retry. That makes it p2 rather than p3.
    • Platform first, so domain:server: the engine's screen-input check must coerce or refuse a non-numeric value for a numeric field, never skip it. The ScreenView.tsx render and post is the domain:ui half, which the PR names.

    Generated by Claude Code

  2. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF

    Claim: PM loop round 1
    Session: session_01Utp6CZMkHvuDeVYgTD73qF (seat) · dev card thread session_01Utp6CZJd2mrcp8CfPkSEEs
    Account: os-bill
    Branch: claude/project-thread-xq1cua (the card thread's designated push branch, the recorded platform exception to claude/issue-<n>-<slug>)
    Worktree: objectstack-issue-30178
    Domain: domain:server
    Seat: domain:server#4
    File surface: (amended on ruling 6648655128, letter A) packages/services/service-automation/src/screen-input-contract.ts (SCREEN_FIELD_VALUE_DOMAINS: percent and currency join number's predicate) and its tests; packages/spec/src/contracts/automation-service.ts and packages/spec/src/automation/builtin-node-config.zod.ts jsdoc / .describe() text only (Zod shape unchanged) plus the regenerated spec artefacts; content/docs/automation/flows.mdx one sentence; ui/packages/app-shell/src/views/ScreenView.tsx the htmlType line only (percent → number input) plus one pin — the maintainer's 「同意」 opens this one ui file for this card, every other ui/** path stays ⛔; two changesets (spec, service-automation). ⛔ rating / slider / progress, docs/adr/**, skills/**, .claude/**, AGENTS.md (stop on breach; explain in the report)
    Container & model: S–M, mode:thread, model: default judgment tier (opus) — dispatch-gates --tier: no path-derived mandate, effort: high
    Clause-②: yes (narrowing) — on ruling 6648655128 (letter A): a percent or currency screen field now refuses any non-number value with invalid_type (no coercion, A′ of 2026-09-13), where it passed every string before; owes a contract-review-tier review before the queue, a BREAKING banner and the ADR-0087 disposition marker. Amended on the ruling; the earlier reading is superseded.
    Metadata-protocol: none
    Responsibility: service-automation's validateScreenInputs compares min/max only for a present number, so a percent value posted as a string skips the bound | none: the REST path refuses a numeric 75 correctly, but the console posts a string and nothing else checks it | every console user of a flow screen with a bounded percent field; measured by HotCRM (hotcrm#2396, Generate Quote discount ceiling) on 18.3.0
    Thread-read: 6641856071
    Premise: holds
    Serial constraints cleared: none — the lane's in-flight claims (#29918 objectql lifecycle, #30062 metadata-protocol, #30106 cli/runtime packages, #30108 plugin-security, #30009 trigger-api, #29997 plugin-auth, #29919 core dispatch-loop / service-messaging / service-queue, #30091 service-settings, #30005/#30006 lint) do not touch screen-input-contract.ts; no open PR touches it or service-automation/src/engine.ts.

    Dispatch order: /mnt/project-files/server-seat4/dispatch-30178.md (project shared folder). Ruling section: no maintainer ruling; triage first-touch audit 6641856071 ("the engine's screen-input check must coerce or refuse a non-numeric value for a numeric field, never skip it"). The PR carries Refs #30178 (engine half); the UI residue stays on this card.

    Posted 2026-11-05T01:43Z.

  3. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF
    os-dev-report

    Status: needs_decision. The dispatch's fork condition is reached before any code: both arms that close the gap change the published screen-field contract text in packages/spec/src/**, and the contract itself says a type member becomes a value domain only by ruling. No branch commits, no PR.

    Readings on objectstack-ai/objectstack bedb96e0:

    • Measured, validateScreenInputs([{ name: 'discount', type, min: 0, max: 60 }], { discount: v }) (a throwaway vitest file in service-automation, deleted after the run):

      type 75 "75" "60" 60 "abc"
      percent max_value [] [] [] []
      currency max_value [] [] [] []
      number max_value invalid_type invalid_type [] invalid_type

      So the card holds for percent and also for currency over REST: any string, "abc" included, passes and the run consumes the suspension. number refuses every string, "60" included.

    • Why: SCREEN_FIELD_VALUE_DOMAINS (packages/services/service-automation/src/screen-input-contract.ts:68-75) holds number and file only, and the bound pass (line 253) skips any value that is not a JS number.

    • The number refusal is maintainer ruling A′ (decision batch Fix sharing.test.ts for discriminated union schema #130 item 1, 2026-09-13), and it says refuse, never coerce. The module docstring (lines 129-170) gives the reason: reading "25" as 25 "would make the bound's verdict depend on a coercion no part of this contract declares". It also says the set is closed and "a member joins it by ruling".

    • The published contract says the same in packages/spec/src/**: ScreenFieldSpec.type / .min / .max jsdoc (packages/spec/src/contracts/automation-service.ts:355-383, "Two members are value domains", "⛔ Not coerced") and ScreenFieldConfigSchema.type / .min / .max (packages/spec/src/automation/builtin-node-config.zod.ts:957, 1029-1039, "the shape refusal above reads 'number' alone"). content/docs/automation/flows.mdx:1305 names the same two shapes.

    • The console half: ui/packages/app-shell/src/views/ScreenView.tsx:710 gives htmlType: 'number' to number and currency only, and line 726 posts Number(...) only for that numeric input. So the console posts currency as a number and percent as a string. Not touched (ui lane paused).

    Why neither arm fits inside the order:

    • Coerce (the order's suggested route: "60" accepted, "75" bound-checked): on number it reverses ruling A′; on percent/currency alone it makes two rules for numeric kinds (number refuses "60", percent takes it). Either way the spec text above becomes false.
    • Refuse (A′'s rule extended to percent/currency): consistent with A′, but the spec text still names two members, and with the console posting every percent value as a string, every console submit of a percent field is refused, "50" included, until the UI half lands. Today an in-range value works. That would turn "lose the dialog on an over-limit discount" into "the quote screen can never be submitted".
    {
      "issue": 30178,
      "status": "needs_decision",
      "branch": "claude/project-thread-xq1cua",
      "pr": null,
      "session": "session_01Utp6CZJd2mrcp8CfPkSEEs",
      "premise_still_valid": true,
      "summary": "Premise holds at bedb96e0 and is wider: percent AND currency accept any string over REST (\"abc\" included), only number refuses strings (ruling A′, refuse never coerce). Closing it either reverses or extends A′, and both edit the published screen-field contract text in packages/spec/src (contracts/automation-service.ts, automation/builtin-node-config.zod.ts). Refusing percent strings in the engine alone would block every console submit of a percent field until the paused UI half lands. Stopped at the fork condition with no code written.",
      "tests": "one throwaway measurement (vitest, service-automation, deleted): 15 cases, table above; no pins written",
      "gates": "none: no diff",
      "dogfood_battery": "n/a: no PR",
      "mcp_calls": "0",
      "api_writes": "1: this os-dev-report comment on #30178 through post-stamped (relay)",
      "open_questions": [
        {
          "question": "Which rule does the engine apply to a percent (and currency) screen field that receives a string?",
          "options": [
            "A: percent and currency join ruling A′'s number domain: a present value that is not a finite JSON number is refused (invalid_type, INVALID_SCREEN_INPUT 400, suspension kept), never coerced. Engine one line plus the spec jsdoc/describe text and flows.mdx. Currency can ship now (the console already posts it as a number). Percent ships only in the same release as the UI half (ScreenView percent to a numeric input, client screenFieldBoundViolations same rule), or console percent submits are all refused. Size S engine plus the UI card.",
            "B: percent and currency read a finite numeric string as its number for the bound pass and refuse a non-numeric string; the value is bound as posted. Fixes the business loss now with no console regression. Cost: reverses A′'s never-coerce for these two kinds, two rules for numeric kinds (number refuses \"60\", percent takes it), spec text edit, and a half state to remove once the UI half lands. Size S.",
            "C: no engine change; the card waits for the UI half alone (percent posts a number), which the existing bound pass then checks. REST callers can still post \"abc\" or \"75\" to percent/currency and pass. Size S, ui lane."
          ],
          "recommendation": "A, because it keeps the maintainer's own A′ rule as the one rule for every numeric kind and needs no coercion the contract does not declare. Sequence it: currency and the spec text now, percent together with the UI half when the ui lane reopens. B only if the over-limit discount loss must be fixed before the ui lane reopens. The dispatch did not carry the decision-axes framework, so this recommendation is not graded against those axes."
        }
      ],
      "security_findings": "none",
      "out_of_scope_findings": [
        "UI residue (domain:ui, not touched): ScreenView.tsx:710 maps percent to a text input and posts a string; client screenFieldBoundViolations compares a present number only."
      ]
    }

    Report taken 2026-11-05T01:46Z.


    Generated by Claude Code

  4. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF

    Seat note · 2026-11-05T01:47Z · the dev's needs_decision (6647359070) stops this card before any code.

    • Measured: the engine passes any string on a percent or currency screen field ("abc" and "75" alike), and refuses every string on number ("60" too), the latter under the maintainer's ruling A′ of 2026-09-13 (refuse, never coerce; a type member joins the value domain only by ruling). Every route changes published contract text in packages/spec/src (contracts/automation-service.ts, automation/builtin-node-config.zod.ts), so the dispatch order's fork condition holds.
    • Options on the table: A, percent/currency join A′'s numeric domain (refuse non-numbers, no coercion; percent must ship with the UI half, which is paused); B, check a numeric string as a number (reverses A′'s no-coercion); C, leave the engine and wait for the UI half.
    • Waiting on: a decision analysis for the maintainer, since 01:48Z. The claim and branch claude/project-thread-xq1cua (no commits) stay; nothing is dispatched until the ruling is posted here. If it does not arrive by this seat's sign-off, the card is released with a Release: line.
  5. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    review#1 · os-bill · session_01Utp6CYb4TMX5Q4YFXFHTdf

    Ruling: needs_decision fork on #30178 (project review thread 「决定百分比和货币字段怎么校验字符串 #30178」) · letter A · maintainer 「同意」 2026-11-05T03:12Z

    The maintainer's own ruling, given in the project thread at 2026-11-05T03:07Z on the review thread's six-section analysis (/mnt/project-files/decisions/30178-百分比货币屏幕字段字符串校验-呈批.md, recommendation A). Marker: maintainer-ruled, presented by the review thread (session session_01Utp6CYb4TMX5Q4YFXFHTdf); not a proxy ruling. Completes ruling A′ (5651909056, decision batch #130 item 1, 2026-09-13) for the other two numeric screen-field kinds; reverses nothing.

    What the review thread re-verified (code read at dfafae81 on main; the card thread measured at bedb96e0, report 6647359070)

    • SCREEN_FIELD_VALUE_DOMAINS (packages/services/service-automation/src/screen-input-contract.ts) holds number and file only, and the bound pass skips any value that is not a finite JS number. So a percent or currency screen field admits any string, "abc" included, and the run consumes its suspension; number refuses every string (A′).
    • The console (ui/packages/app-shell/src/views/ScreenView.tsx, the htmlType line) gives a numeric input to number and currency only, so percent renders as text and is posted as a string; the client bound check (screenFieldBoundViolations) compares a present number only. ui/ is this repository's own source tree, compiled by Type Check UI against the PR's spec (AGENTS.md Post-Task Checklist step 4).
    • Ruling A′'s own heading reads "a non-number for a numeric field is refused, never silently passed"; its item 2 spells number-typed. The sentence "the set is closed … a member joins it by ruling" is the implementer's docstring, not the maintainer's word.
    • The spec already names the three numeric kinds as one group: MEASURE_FIELD_TYPES = ['number', 'currency', 'percent'] (packages/spec/src/data/aggregation-policy.ts); the object field form shows min/max on that group plus rating/slider/progress.
    • ScreenFieldConfigSchema.type stays z.string().optional() under every arm: the Zod SHAPE does not change, only the jsdoc / .describe prose and its generated mirrors. Neither packages/spec/** nor ui/** is on the Tier H register (scripts/pm/check-governed-merges.mjs); the landing owes a contract-review-tier record for the narrowing, not a human merge.
    • In-tree example flows declare no percent / currency screen field; the known affected declaration is hotcrm's Generate Quote discount.

    Governing text

    • Ruling A′ 5651909056 (heading and item 2) and its clarification 5652004815: refuse, ⛔ never coerce; "skipping the dialog is refused too" with no qualifier.
    • Triage 6641856071: "the engine's screen-input check must coerce or refuse a non-numeric value for a numeric field, never skip it."
    • AGENTS.md Prime Directive 12 (contract-first: ⛔ no lenient alias or fallback in a consumer to tolerate off-spec input) and Post-Task Checklist step 4 (a change that reds the Console under ui/ ships together with the ui/ fix in the same PR, or does not ship).
    • ADR-0114 D2 (field-level error-code catalog: invalid_type already exists; no new code).

    The ruling: A

    percent and currency join number in the screen-input value domain: a present value for a type: 'percent' or type: 'currency' screen field that is not a finite JSON number is refused on resume with the catalog's invalid_type inside INVALID_SCREEN_INPUT (400, retryable, suspension kept), ⛔ never coerced — the same predicate and the same sentence number already has. The same PR maps percent to the console's numeric input (htmlType: 'number', carrying min/max, posting a number), so the client bound check applies to it without a further change. The maintainer's 「同意」 covers that one ui/ file on this domain:server card while the ui lane is paused.

    Fixed here:

    1. Engine: two rows in SCREEN_FIELD_VALUE_DOMAINS (percent, currency; predicate = finite JSON number; expected a number). The module docstring's "one member / two members" wording becomes the three numeric members plus file. ⛔ Not widened to rating, slider, progress or any other hint: the console has no numeric widget for them today; they stay widget hints until their own ruling.
    2. Pins (service-automation): on percent and on currency, "75" → invalid_type (not max_value), "abc" → invalid_type, "60" → invalid_type, 75 → max_value, 60 → []; text, untyped and rating fields still admit a string; a hidden field is not judged; the ablation (remove the two rows) reds the negative pins.
    3. Console: ScreenView.tsx — percent takes the numeric input path (one line) with one pin: a percent field renders type="number" carrying min/max and submits a number. A dedicated percent widget (suffix, formatting) is the ui lane's later choice and is not decided here.
    4. Spec prose: ScreenFieldSpec.type / .min / .max jsdoc (packages/spec/src/contracts/automation-service.ts), ScreenFieldConfigSchema.type / .min / .max jsdoc and .describe (packages/spec/src/automation/builtin-node-config.zod.ts), the sentence in content/docs/automation/flows.mdx, and the regenerated spec artifacts (AGENTS.md § Touched packages/spec). No Zod shape change, no new key, no tombstone, no ADR-0087 migration entry.
    5. Landing: one PR on the card thread's branch, Fixes #30178 (both halves land), Clause-②: yes (narrowing) with the BREAKING banner and the ADR-0087 disposition marker in both changesets (@objectstack/spec, @objectstack/service-automation; level per the launch-window guard — A′'s landing took minor on both), Metadata-protocol: none, Ruling-ref: <this comment id>; a contract-review-tier record on the final head before the queue.
    6. ⛔ Not: coercing a numeric string for the bound pass (B — reverses A′ and leaves a string in the flow variable); leaving the engine untouched (C); shipping the percent refusal without the console line (every console percent submit would be refused).

    Why A, on the four axes (business standpoint)

    • ① Long-term soundness (≥50%). One rule for the numeric kinds, the same rule the object protocol already states by grouping number/currency/percent. A′'s heading already said "numeric field"; A completes it. Coercion in the engine would be the consumer-side leniency Prime Directive 12 forbids and would hide the console defect that caused the loss.
    • ② Measured business pull. HotCRM measured on 18.3.0; triage raised it to p2 because an over-limit discount loses the run. Every console user of a bounded percent screen field; API and MCP callers posting strings to currency fields today pass unjudged.
    • ③ AI-error prevention. The failure direction stays loud (400, retryable, suspension kept). The one trap — engine first, console later — is closed by shipping both halves in one PR under Type Check UI and the console pin gates.
    • ④ Startup no-sprawl. Two table rows, three prose sites, one console line, two pins; no new code path, error code or seam; S.

    ① 长远:数值类屏幕字段只有一条规则,与对象字段同组(MEASURE_FIELD_TYPES);A′ 标题本就写「numeric field」,A 是补全不是推翻;引擎强转是首要指令 12 禁止的消费端宽容,且盖住控制台的根因。
    ② 拉动:HotCRM 在 18.3.0 实测、分诊升 p2(折扣超限丢运行);每个声明带上下限 percent 屏幕字段的客户;API/MCP 调用方往 currency 发字符串今天无人判。
    ③ 防错:失败方向响亮(400 可重试、挂起保留);「引擎先发、控制台后发」的坑由同 PR 两半 + Type Check UI 封住。
    ④ 不扩散:值域表两行、prose 三处、ui 一行、两钉;不加路径、不加错误码、不加接缝;S。
    Prior rulings read: "screen field number refuse coerce string gap", "numeric field bound resume" → A′ 5651909056 + 5652004815 (#17306); #25801 (file joined the set); triage 6641856071; dispatch 6647305770; report 6647359070; seat note 6647378538.

    Execution after the ruling

    • domain:server seat 4 holds the claim (6647305770) and dispatches from it: the card body's first line becomes Ruled: <this comment id> · letter A · <UTC>; the dispatch order's file surface widens to the engine module and its tests, the two spec files and their regenerated artifacts, content/docs/automation/flows.mdx, ui/packages/app-shell/src/views/ScreenView.tsx and its test, and two changesets. Still ⛔ docs/adr/**, skills/**, .claude/**, AGENTS.md, every other ui/** path. ⛔ Ruling is not dispatching.
    • The card thread resumes on claude/project-thread-xq1cua with the pins and the ablation above; Refs becomes Fixes #30178.
    • hotcrm re-measures hotcrm#2396 on the fixing pin, as the card body says; nothing in that repository is touched from here.
    • Not reopened: the number and file rules; rating / slider / progress as widget hints; a dedicated percent widget; the ui lane's pause for anything but this one file.

    Generated by Claude Code

  6. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF
    os-dev-report

    Status: done on ruling 6648655128 (letter A). Draft PR #30269, head 3eb877a57, Fixes #30178.

    {
      "issue": 30178,
      "status": "done",
      "branch": "claude/project-thread-xq1cua",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/30269",
      "session": "session_01Utp6CZJd2mrcp8CfPkSEEs",
      "premise_still_valid": true,
      "summary": "Ruling A implemented. currency and percent join number in SCREEN_FIELD_VALUE_DOMAINS on the same predicate (finite JSON number, invalid_type inside INVALID_SCREEN_INPUT, never coerced); rating/slider/progress/text/untyped unchanged. ScreenView.tsx renders percent as the numeric input (min/max carried, posts a number). Spec jsdoc and the type describe in contracts/automation-service.ts and automation/builtin-node-config.zod.ts name the three numeric kinds (no Zod shape change), the reference page is regenerated, and three flows.mdx sentences that named number alone are updated. Two changesets (service-automation + app-shell; spec), patch per Metadata-protocol none, BREAKING banner and ADR-0087 not-required (no-migration-prescription).",
      "tests": "service-automation: pnpm test exit 0, typecheck exit 0; screen-input-contract + screen-input-file-field 23/23. ui ScreenView + FlowRunner (22 files) 152/152. spec build (declarations on), check:generated -> gen:schema && gen:docs committed; check:test-typecheck green at 6144 MB heap; spec typecheck exit 0. Ablation 1 (delete the currency/percent rows): 4 failed | 13 passed, restored by blob == HEAD. Ablation 2 (delete the percent arm in ScreenView): 1 failed | 14 passed, restored. Both subjects import src relatively, so no dist leg.",
      "gates": "dispatch-gates --commands: 109 derived at 0d70b581; --ran: 108 run, 1 NOT-MEASURED (check:dual-build-cjs-loads exit 3, whole-workspace build), 0 UNRUN. First-pass red check-adr-0087-registration fixed on 3eb877a57 (FROM/TO label beside no-migration-prescription). First-pass exit 3 re-run green after prerequisites: check-plugin-teardown-shape --self-test, spec check:skill-examples, ui vitest scripts/ (170 files / 4985 tests). Changeset gates, closing-keyword-parity, nul-bytes, issue-citations, doc-authoring re-run on 3eb877a57: exit 0. pnpm lint exit 0. NOT MEASURED locally: @object-ui/app-shell type-check (TS2307 on unbuilt ui siblings), left to Type Check UI. CI check-runs: in_progress at report time.",
      "dogfood_battery": "0d70b581 · VERDICT command-exit 1, 20m44s · Test Files 1 failed / 418 passed / 3 skipped (422) · Tests 1 failed / 3491 passed / 36 skipped (3528) · the failure is permission-set-clone-door.dogfood.test.ts (platform set copy, 422 on sys_comment), main's own red at the merge base 797f9257 (Dogfood Regression Gate 2/3 fails on main), filed #30255, revert PR #30262 open; re-runs red alone 1/11; not this diff",
      "mcp_calls": "0",
      "api_writes": "2 REST writes: pr_create #30269 through the fleet relay (dispatch, draft, no assignees, no labels); this os-dev-report comment on #30178 through post-stamped (plus the earlier needs_decision comment 6647359070); git push is not a REST write",
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": [
        "carrier: #30255 · main red, Dogfood Regression Gate (2/3), permission-set-clone-door after PR #30222; noted, not filed"
      ]
    }

    Deviations, for the seat: the console pin went into the existing bound harness ScreenView.boundHelpReference-9248.test.tsx as a top-level describe (AGENTS.md: a new pin for a component with a shared harness goes into that file); @object-ui/app-shell rides the service-automation changeset, since its behaviour moves too; flows.mdx changes three sentences rather than one, because the other two would have stated the number-only rule after this PR; the session-context's assignee and reviewer step was skipped (dispatch Writes line: no MCP writes, no assignee).

    Report taken 2026-11-05T04:21Z.


    Generated by Claude Code

  7. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF

    Landed · PR #30269

    Reading Value
    PR #30269 MERGED 2026-11-05T05:19Z, merge commit 4be973424; Fixes #30178 closed this card. Contract review PASS 6649966548 on the landed head.
    main source packages/services/service-automation/src/screen-input-contract.ts:81 ['percent', { admits: isFiniteNumber, expected: 'a number' }] (with currency beside it)

    State change: pm:dispatched removed, assignee cleared. Ruling 6648655128 (letter A) is carried out in full, the ScreenView.tsx line included, so no ui residue is left.

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

    bugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions