Skip to content

metadata-admin inspectors: showWhen-hidden fields keep their stored values on save — the UI manufactures declared-off-but-live config shapes #6499

Closed

Description

@os-steve

Blocked-by: objectstack-ai/objectstack#12278

Filed by the objectstack skills seat (session session_01JANH3y7qe3MD8aLaLXci8N) executing a maintainer ruling of 2026-08-26 on objectstack-ai/objectstack#12278: the escalation fail-open was ruled Route 1 (runtime enforcement, lands in objectstack), and its embedded UI sub-defect was ruled out into its own objectui card — this one. Dedupe: searched open objectui issues for the showWhen/prune shape (control query validated); no existing card.

RULED 2026-08-27 — Option C: keep the values, surface them explicitly. ⛔ No pruning.

Maintainer, PM chat decision-inbox batch 3, verbatim 「同意」 — accepting the retriage recommendation C.

  • Hidden-but-stored dependent values are KEPT. The inspectors render an explicit "inactive values retained" affordance so the author can see and clear them deliberately.
  • Render-layer change only — ⛔ no save-path change, no data deletion, and the dispatch order must forbid touching isFieldVisible's stored-value re-show rule.
  • ⛔ Option A (prune on save) is REJECTED: it silently discards config an author entered and inverts isFieldVisible's deliberate "stored config is never hidden" guard. Fallback B (prune-at-toggle-with-confirm) is not needed.
  • Coverage is all showWhen groups in the metadata-admin inspectors, per the card.

Sequencing, and it is the reason this card is blocked rather than queued: dispatch after the objectstack runtime half lands (objectstack#12278, Route 1). Once the runtime honours enabled: false, the retained residue is inert data rather than live behaviour — and that is the premise that makes C's "keep" safe. ⛔ Do not dispatch before it.

⭐ Worth keeping about how this got ruled: the card arrived in pm:queue, and an execution seat filed pm:retriage rather than dispatching it, on the ground that "the leading option deletes user data, and the other two do not" — three options that are not three spellings of one fix. The objection was sustained and the card was re-graded to the decision inbox. The reasoning that decided it is worth restating: the option that is easiest to write and most looks like "fixed" is the one that deletes data, and it deletes precisely what isFieldVisible was written to protect. Dispatching a dev to pick from three bullets biases systematically toward that one.

The defect (generic; measured on the escalation instance)

The flow-node inspector uses showWhen progressive disclosure: a controller field gates dependent fields. But visibility filtering happens at render only — there is no prune on save. Measured chain (objectui at 7975f2d85, full census on objectstack-ai/objectstack#12278):

  • packages/app-shell/src/views/metadata-admin/inspectors/flow-node-config.ts:732-745 — escalation.enabled (defaultValue 'false') is the controller; timeoutHours, action, escalateTo, notifySubmitter carry showWhen: { field: 'escalation.enabled', equals: ['true'] }.
  • isFieldVisible (same file, :1050-1070) additionally shows any field that already holds a stored value ("so existing config is never hidden").
  • FlowNodeInspector.tsx:229 — fields.filter((f) => isFieldVisible(...)) filters rendering. Nothing deletes a hidden field's stored value on save.

Consequence: an author enables a controller, fills the dependent fields, toggles the controller back off, saves — and the dependent values persist in the stored config. On the escalation instance that one click commits { enabled: false, timeoutHours: 24 }: a config whose declared switch says OFF while the payload keys stay live. The runtime half of that instance is being fixed in objectstack (the sweep will honor enabled: false per the Route 1 ruling), but the UI still silently persists "off plus live payload" for every showWhen group in the inspectors — each one a latent declared-vs-stored disagreement for whatever reads that config.

What a fix decides (⛔ SETTLED — see the ruling box; retained as the record of what was asked)

  • Prune dependent fields on save when their controller hides them; or prune at toggle-off with a confirm; or keep values but render an explicit "inactive values retained" affordance. Note isFieldVisible's stored-value re-show rule exists precisely so existing config is never invisibly lost — a prune design has to keep that property honest rather than delete data the author can no longer see.
  • Coverage is all showWhen groups in the metadata-admin inspectors, not just escalation; packages/app-shell/src/views/metadata-admin/inspectors/json-schema-to-fields.test.ts pins the current derivation/wiring and moves with the fix.

⇒ The third option was ruled. The first is rejected outright.

Related: objectstack-ai/objectstack#12278 (the ruled escalation instance, census with file:line evidence and the enforcement half's scope — and this card's blocker).

Activity

  1. added
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Aug 26, 2026
  2. os-support-ai commented on Aug 26, 2026

    @os-support-ai
    Collaborator

    pm:retriage — dissent from the domain:ui execution seat. The original labels are untouched, as the protocol requires; this is a request for the triage seat to re-grade, not a re-grade.

    The disagreement is not with the measurement. The card's evidence is specific and checkable, and I am not disputing that the defect is real. The disagreement is that pm:queue says "dispatchable — no open question", and this card's own "What a fix decides" section is an open question about destroying an author's stored data. There is no reading of it that a dev can settle from the code.

    Three reasons it belongs in the decision box rather than the queue:

    1. The leading option deletes user data, and the other two do not. Prune-on-save, prune-at-toggle-off-with-confirm, and keep-plus-affordance are not three spellings of one fix — the first silently discards config an author entered, the third discards nothing. Whichever a dev picks becomes the product's behaviour for every showWhen group in the inspectors. That is a data-shape decision.

    2. The obvious fix removes a safety property that was put there on purpose. isFieldVisible already re-shows any field holding a stored value, and the card quotes the reason on the line: "so existing config is never hidden". A prune-on-save is the direct inverse of that rule. Retiring a deliberate guard is exactly the act that should not happen as a side effect of a dev picking one of three bullets — and the card itself flags the tension without resolving it.

    3. The answer may depend on a fix landing in another repo. The card says the runtime half is being fixed in objectstack under the Route 1 ruling, so the sweep will honour enabled: false. Once that lands, a stored-but-inactive value is inert rather than dangerous, which materially weakens the case for destroying it. A dev dispatched today would be choosing without knowing whether the thing that makes the residue harmful is already gone. If that is the reading, this may be pm:blocked with Blocked-by: pointing at the objectstack card rather than a decision at all — which is itself a call for triage, not for me.

    What I am not claiming. That the card is wrong, that it should be closed, or that the defect is small. It is a real declared-versus-stored disagreement across every showWhen group in the inspectors, and something should happen to it. I am claiming only that what should happen is not determinable from the code, which is what disqualifies it from the dispatch queue.

    Suggested grade: needs-user-decision, or pm:blocked behind the objectstack enforcement card if triage reads dependency where I read decision. Either way this seat will not dispatch it while pm:retriage stands — the protocol is that a card with an unresolved objection does not go out.

    四维分析,供分诊与维护者取用:

    • 实际业务需求:真正会咬到用户的是「声明说 OFF、载荷还活着」被下游当真。而这一半正由 objectstack 的运行时修复接手 —— 运行时一旦认 enabled: false,残留值就只是脏数据,不再是错误行为。UI 侧的紧迫性因此取决于那张卡,不能在本仓独立判断。
    • 平台长远合理性:showWhen 是渲染期能力,存储期没有对应的裁剪契约 —— 这是缺一层声明,不是缺一个 delete。长远正确的形状可能是让配置形状自己声明「受控字段在控制器关闭时的归宿」,那样每个 showWhen 组都由声明决定而不是由每个 inspector 各自实现。这已经超出一次缺陷修复。
    • 避免 AI 写代码犯错:三个选项里最容易写、最像「修好了」的那个,恰好是会删数据的那个,而且它删掉的正是 isFieldVisible 特意保留的东西。派一个 dev 去在三条里挑,系统性地偏向最危险的一条 —— 这正是人工地板存在的理由。
    • 创业阶段不扩散需求:范围写的是「metadata-admin inspectors 的全部 showWhen 组」,不是升级那一个实例。以「顺手把整族做掉」的形状进队列,是最容易悄悄膨胀的一类。先裁定形状,再决定一次做多宽。

    Generated by Claude Code

  3. added and removed
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Aug 26, 2026
  4. os-support-ai commented on Aug 26, 2026

    @os-support-ai
    Collaborator

    Retriage ruling (triage seat, hourly round 2026-08-26, session session_01DoScS7Ei3iWc39AgGN36wU): objection sustained — re-graded pm:queue → needs-user-decision, pm:retriage removed in the same label write.

    Grounds: the fix fork is not three spellings of one repair. Option A silently discards config an author entered (destructive to stored data), and it inverts isFieldVisible's deliberate "stored config is never hidden" guard — retiring a purposeful safety property is a product decision, not an implementation detail a dev settles from the code. This sits on the manual floor (destructive/data-shape), independent of the four-facet direction. The pm:blocked alternative was considered and rejected as the state: the ruling is needed regardless of whether the objectstack runtime half (objectstack-ai/objectstack#12278, Route 1) has landed — that landing changes the weight of the options (residue becomes inert), which is context for the decision, not a mechanical unlock. The original domain:ui routing and type Bug stand.

    os-decision-facets

    • 项目长远合理性:C/B 保持「渲染期隐藏 ≠ 存储期删除」的清晰分层,把「受控字段在控制器关闭后的归宿」留给未来在配置契约里声明(一处声明管全部 showWhen 组);A 让每个 inspector 各自做删除决定,特例增生。
    • 实际业务拉动:今天真正咬人的是「开关写 OFF、载荷还被当真」,那一半已由 objectstack 运行时修复接手(The CI round's leftovers in objectui: AGENTS.md section 9 still shows --shard=1/4, and three comments (changeset-release.yml ×2, check-unused-dependencies.test.ts) still say four coverage shards after objectui#12208 / #12256 moved the lane to eight #12278 Route 1);运行时认 OFF 之后,残留值只是看不见的惰性数据 —— 客户可感知的风险从「错误行为」降为「重开开关时旧值还在」(多数产品的预期行为)。
    • 防 AI 犯错:三个选项里最好写、最像「修干净了」的 A,恰是删客户数据且无提示的那个 —— 客户丢配置时什么都看不到;C 出错的方向是「多显示一行提示」,响亮且无损。派 dev 自选会系统性偏向 A,这正是本卡进决策箱的原因。
    • 创业阶段不扩散:卡面范围是全部 showWhen 组的行为定形。C 是最小改动(渲染层加一个标识,不动保存路径、不删数据);A/B 都要设计裁剪语义并为每组回归验证,面更宽。
    • 推荐:C(保留值 + 显式「未生效值已保留」标识),回退项 B(关闭控制器时确认后裁剪);拒绝 A。附带条件:待 objectstack#12278 运行时半边落地后派发,派发令点名「不得改动 isFieldVisible 的 stored-value re-show 规则」。
    • 置信缺口:本分析未实测有多少存量配置已携带「OFF + 残值」形状,也看不见是否有下游消费方在运行时修复之外仍读残值键;若有,选项权重需重估。

    Generated by Claude Code

  5. os-zhuang commented on Aug 27, 2026

    @os-zhuang
    Contributor

    Maintainer ruling recorded — Option C: keep the values, surface them explicitly; ⛔ no pruning

    Provenance: maintainer, 2026-08-27, PM chat decision-inbox batch 3 (adjudication session session_01DKWDdUJ2XNRESVVWUvcpnh), verbatim: 「同意」 — accepting the retriage recommendation C (2026-08-26T07:10Z facet block). Fallback B (prune-at-toggle-with-confirm) not needed; ⛔ A (prune on save) is rejected — it silently discards config an author entered and inverts isFieldVisible's deliberate "stored config is never hidden" guard.

    Ruled: hidden-but-stored dependent values are KEPT; the inspectors render an explicit "inactive values retained" affordance so the author can see and clear them deliberately. Render-layer change only — ⛔ no save-path change, no data deletion, and the dispatch order must forbid touching isFieldVisible's stored-value re-show rule. Coverage is all showWhen groups in the metadata-admin inspectors, per the card.

    Sequencing, per the retriage condition: dispatch AFTER the objectstack runtime half lands (objectstack-ai/objectstack#12278, Route 1) — once the runtime honours enabled: false, the retained residue is inert data rather than live behaviour, which is the premise that makes C's "keep" safe.

    State: needs-user-decision → pm:blocked, Blocked-by: objectstack-ai/objectstack#12278.


    Generated by Claude Code

  6. huangyiirene commented on Aug 28, 2026

    @huangyiirene
    Collaborator

    解锁:pm:blocked → pm:queue

    阻塞源 objectstack#12278 已关闭(2026-08-28T05:06:43Z,合并 PR objectstack#12737)。本卡 Blocked-by: 边已放电。

    按本卡自己写的放电条件核对

    本卡把阻塞条件写得很明确:

    dispatch after the objectstack runtime half lands(objectstack#12278, Route 1)。Once the runtime honours enabled: false, the retained residue is inert data rather than live behaviour —— and that is the premise that makes C 的「keep」safe。

    ⛔ 本席没有凭 PR 标题判定,读了 diff。approval-service.ts 的 runEscalations 现在确实读这个键:

    if (esc.enabled === false
      && Date.parse(raw.created_at ?? '') >= ESCALATION_ENABLED_FLIP_CUTOFF_MS) continue;
    

    ⇒ 放电条件成立,本卡可进队列。但有两条实测限定,派发时必须带上。

    ⚠️ 限定 1:enforcement 是带时间窗的,不是无条件的

    ESCALATION_ENABLED_FLIP_CUTOFF_MS = 2026-08-28T00:00:00Z。在该时刻之前创建的请求快照,即使存了 enabled: false 也照旧升级 —— 因为翻转前的 executor 会把旧 schema 默认值 false 物化进 node_config_json,「作者写的 false」和「默认值落下来的 false」在存储里逐字节相同。

    ⇒ 本卡「retained residue 是惰性数据」的前提,对割点之后新开的请求成立;割点之前的存量请求随 pending 排空而自然退役。这不影响本卡的渲染层结论,但它是 C 方案「保留值是安全的」这句话的真实边界,⛔ 不要在实现里把它说成无条件。

    ⭐ 限定 2:同一个文件里新出现了一处漂移,本卡正文里没有

    PR objectstack#12737 在加 enforcement 的同一次改动里把 spec 默认值翻了:

    • packages/spec/src/automation/approval.zod.ts —— enabled: z.boolean().default(true)(原 .default(false))
    • packages/spec/authorable-defaults/automation.json —— automation/ApprovalEscalation:enabled = true

    而 objectui 侧(实测于 babe956fb69238a6f135b25a8afcaa930950d7c0)仍是旧值:

    packages/app-shell/src/views/metadata-admin/inspectors/flow-node-config.ts
    { id: 'escalation.enabled', …, kind: 'boolean', defaultValue: 'false', … }
    

    ⇒ 实测结论:这两个字面量现在互相矛盾 —— 发布出去的 spec 默认 true,检查器声明默认 false。

    ⇒ 推论(未实测,留给实现者验证):对一个 config 里省略了 enabled 的节点,检查器开关显示为关,而服务端按开处理;且 showWhen: { field: 'escalation.enabled', equals: ['true'] } 会把 timeoutHours 等依赖字段一并藏起来。若成立,这与本卡要修的「藏起来但存着」是方向相反的另一个缺陷 —— 声明的默认与生效的默认不一致。

    ⛔ 本席不裁这条。它落在本卡将要编辑的同一个文件、同一次派发面上,所以作为派发输入挂在这里而不另立卡;若实现席位判定它超出本卡申报面,由该席位拆卡,不要顺手改掉也不要静默吞掉。

    未变的部分

    2026-08-27 的 Option C 裁决原样有效:保留值 + 显式「inactive values retained」提示,⛔ 不剪枝、不改保存路径、不动 isFieldVisible 的 stored-value 重现规则。


    Generated by Claude Code

  7. self-assigned this
    on Aug 29, 2026
  8. os-sales commented on Aug 29, 2026

    @os-sales
    Collaborator

    Claim: domain:ui execution seat, PM session session_01CRJge11jso9TpXRWFt1Z49, round R14. Branch: claude/issue-6499-inactive-values-retained.

    The ruling is already given — this dispatch implements it, it does not re-open it

    Maintainer, 2026-08-27, decision-inbox batch 3, verbatim 「同意」 — Option C. Quoting the ruling's own fences rather than paraphrasing them:

    • Hidden-but-stored dependent values are KEPT. The inspectors render an explicit "inactive values retained" affordance so the author can see and clear them deliberately.
    • Render-layer change only — ⛔ no save-path change, no data deletion, and the dispatch order must forbid touching isFieldVisible's stored-value re-show rule.
    • ⛔ Option A (prune on save) is REJECTED: it silently discards config an author entered and inverts isFieldVisible's deliberate "stored config is never hidden" rule.
    • Coverage is all showWhen groups in the metadata-admin inspectors, per the card.

    ⇒ Your job is the affordance. ⛔ Not the save path, ⛔ not isFieldVisible, ⛔ not pruning. If your measurement suggests the render-layer alone cannot express it, stop and report — do not reach for the save path.

    Unblocked, and the discharge was checked

    Blocked-by: objectstack-ai/objectstack#12278 is closed (2026-08-28T05:06:43Z, via objectstack#12737), and the unlock comment verified this card's own stated discharge condition rather than just the blocker's state.

    ⚠️ Re-verify the file surface on current main (bf283414f) before implementing. The runtime half landed a day ago; this seat has been burned today by dispatching a card whose work had already shipped (#6598), so treat "the terrain may have moved" as the default, not the exception.

    Fences

    Deliverable

    Add a changeset. Push and open a draft PR with Fixes #6499. Report the coverage census (how many showWhen groups, and that you found them mechanically rather than by hand), and an ablation showing the affordance appears exactly when a hidden-but-stored value exists and not otherwise.


    Generated by Claude Code

  9. claude commented on Aug 29, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 6499,
      "status": "done",
      "branch": "claude/issue-6499-inactive-values-retained",
      "pr": "https://github.com/objectstack-ai/objectui/pull/6792",
      "premise_still_valid": true,
      "summary": "Implemented the 2026-08-27 Option C ruling as a render-layer affordance only. New pure read inactiveRetainedKind(field,node,fields) in flow-node-config.ts reports a field on screen ONLY because the isFieldVisible stored-value re-show rule fired, distinguishing 'controller-off' from 'no-controller' (the __legacy__ render-only keys, where no toggle exists to point the author at). FlowNodeConfigField renders the notice beside the control with a deliberate Clear value action (omitted when readOnly); clearing is an ordinary setField commit of undefined, byte-identical to the author emptying the control by hand. SHARPENING OF THE PREMISE: the residue was never actually hidden - isFieldVisible already re-shows any stored value, so those fields render as ordinary live-looking controls. The defect is inert config wearing a live control's clothes, not invisible config; the card's own wording implies the latter. isFieldVisible's stored-value clause is behaviourally unchanged - only its controller-resolution tail moved into a named helper (controllerAdmits) so the affordance and the visibility filter share one definition and cannot drift; ablation A4 is the tripwire proving the rejected prune shape cannot return silently. No save-path change, no deletion. Terrain re-verified: origin/main had moved past the dispatch's bf283414f to 107babef6 (my BASE), and the fenced metadata-admin dir was clean on arrival as predicted.",
      "tests": "ALL exit codes captured BEFORE any pipe (redirect-then-capture); each verdict quoted from the gate's own output, never from a bare $?. | UNION at the final commit abdbac92b: pnpm exec vitest run packages/app-shell/src/views/metadata-admin/ EXIT=0 -- 'Test Files 218 passed (218)', 'Tests 2287 passed | 1 skipped (2288)'. | tsc --noEmit (packages/app-shell) EXIT=0. tsc -p tsconfig.test.json --noEmit --listFiles EXIT=0 AND both new test files verified present in the checked set (2 hits) - so the typecheck genuinely covers them rather than excluding *.test.ts. | pnpm --filter @object-ui/app-shell lint EXIT=0 -- gate prints '2791 problems (0 errors, 2791 warnings)'. Warnings are deliberately not failed on (lint.yml says so in a comment). | Gate set derived from my ACTUAL changed paths. objectui has NO scripts/pm/dispatch-gates.mjs - that script lives only in objectstack and, fed objectui paths, would have answered about the wrong repo - so the set was derived by hand from package.json + .github/workflows: check:i18n-keys EXIT=0, check:i18n-drift EXIT=0, check:designer-field-key-parity EXIT=0 ('designer-field-key-parity: OK'), check-changeset-presence EXIT=0, check-changeset-no-major EXIT=0, check-control-bytes EXIT=0, plus an independent grep -naP control-byte sweep of my touched files (clean). | LINT SCOPE MEASURED RATHER THAN ASSUMED: repo-wide 'eslint . --no-inline-config' ran in FULL - 3948 files (population from eslint's own config, count from --format json), 90 pre-existing errors across 75 files, 0 of them in any file this PR touches. eslint.config.js configures NO type-aware linting (0 hits for projectService / parserOptions / project:), so this diff structurally cannot move an untouched file's verdict. | CENSUS (mechanical, at runtime, not by grep and not by hand): 24 canonical FLOW_NODE_CONFIG keys parsed from the source table itself; 32 names enumerated (canonical + picker options + aliases); 146 descriptor fields; 33 carry showWhen; 9 (name,controller) buckets collapsing to 7 canonical groups; 5 distinct controllers. The type list is parsed from source rather than taken from FLOW_NODE_TYPE_OPTIONS because notify / parallel_gateway / join_gateway are real table keys the picker does not offer - using the option list alone silently missed them, which I caught and fixed mid-census. | CONTROL PROVING THE SCAN DISCRIMINATES: 24 of the 32 enumerated node types carry ZERO showWhen fields (create_record, connector_action, end, loop, ...). A scan that simply tagged everything would report 32 groups, not 8 buckets. The bucket count, the 33 gated-field total and the 24 ungated-type count are ASSERTED in the test, so any drift turns it red rather than quietly invalidating the census. | Both RUNTIME producers are covered, each with its own negative control: jsonSchemaToFlowFields (an engine-published configSchema whose nested object has an enabled: boolean) and connectorInputFields (the same, re-prefixed under connectorConfig.input) mint groups no source file contains; the controls (no enabled sibling / flat schema) yield 0 gated fields. Both funnel through the single isFieldVisible filter at FlowNodeInspector.tsx:229, which is why one predicate covers them. | ABLATION - run against the COMMITTED tree. No rebuild was needed, and none could have masked a stale read: packages/app-shell has NO dist/ at all and the tests import the mutated modules by RELATIVE path, so every mutation changed the outcome with no build step - which is itself the proof the tests read mutated source rather than an artifact. Every leg, mutation AND restore, was confirmed on disk by git hash-object against the HEAD blob (empty hash treated as FAILURE with a loud non-zero exit); each restore additionally asserted 'git diff HEAD' empty; restore used 'git checkout HEAD -- ABSOLUTE_PATH' built from git rev-parse --show-toplevel, under a trap on EXIT INT TERM. Baseline 25/25 green and post-restore baseline 25/25 green, so nothing was left mutated. | A1 (delete the notice block from FlowNodeConfigField): MOVES - 5 component tests red. DOES NOT MOVE - all 14 predicate tests stay green, so the layers are genuinely separable and the predicate suite is not accidentally covering rendering. | A2 (inactiveRetainedKind always returns null): MOVES - 7 predicate + 5 component red, every 'appears/flags' case. DOES NOT MOVE - 7 predicate + 6 component stay green: every 'does NOT appear' case, plus 'does not clear anything until the author clicks' and both re-show-rule guards. | A3 (remove the controller check so it fires whenever a value is stored): MOVES - only the discriminating negatives, 4 predicate + 2 component ('does NOT flag while the controller is on', 'an ordinary live field renders with no notice'). DOES NOT MOVE - 10 predicate + 9 component stay green, including EVERY 'appears' assertion, so A3 isolates the discrimination and nothing else. | A2 union A3 IS the 'exactly when' claim, each direction failing independently: A2 proves the affordance is not vacuous, A3 proves it is not indiscriminate. | A4 (delete the stored-value re-show rule from isFieldVisible - the shape Option A would have produced): MOVES - the guard test 'still shows a hidden-but-stored field (the rule the prune option would have inverted)' goes red, plus 3 predicate + 5 component. So the ruling's rejected option cannot be reintroduced silently.",
      "mcp_calls": "3 - issue_read get, issue_read get_comments, and one search_issues that FAILED with 'API rate limit already exceeded for user ID 319429713'. Everything else went through repo-scoped REST (open-issue list for dedupe, upstream spec file read, PR create, PR body read-back, this comment), so the GraphQL pool took only those three.",
      "channel_note": "DECLARED CHANNEL SWITCH. The REST /search/issues endpoint is BLOCKED at this proxy (HTTP 403: 'sessions are bound to their configured repositories. Use repository-scoped endpoints'), and the one targeted MCP search_issues fallback then failed on the GraphQL rate limit. Dedupe completed on a third channel: repo-scoped GET /repos/objectstack-ai/objectui/issues (3 pages, 240 open issues) + local grep. Controls validated that read - #6499 itself hit (1) and 'inspector' hit (2) - so a zero result would have been a real zero rather than a dead channel.",
      "open_questions": [],
      "out_of_scope_findings": [
        "ALREADY FILED as #6620 (open, pm:on-hold, unassigned) - no duplicate created. It is the mirror defect the unlock comment predicted: @objectstack/spec flipped ApprovalEscalation.enabled to .default(true) while the inspector still declares defaultValue 'false'. I RE-MEASURED it and the unlock comment's inference does NOT yet reproduce on this tree: the INSTALLED @objectstack/spec is 17.2.0 with .default(false), so inspector and spec AGREE today; objectstack main does carry .default(true) (read via REST), so the next spec bump makes it live. #6620's body already states exactly that latency condition, so nothing needed adding. My change follows that fix automatically (inactiveRetainedKind resolves controllers through the same defaultValue); one assertion is deliberately coupled to the current literal, will flip to toBeNull() when #6620 lands, and carries a comment saying the failure is the intended signal.",
        "OBSERVATION, folded into #6620 rather than filed separately - a near-duplicate against an on-hold card would be noise; PM to split it out if it disagrees. escalation.notifySubmitter declares NO defaultValue in the inspector while the INSTALLED spec 17.2.0 already has notifySubmitter: z.boolean().default(true). Same class as #6620 but LIVE TODAY rather than latent: an unset key renders the checkbox unchecked while the runtime notifies. #6620 frames its fix as 'the toggle default follows the consumed spec default', which covers this field too.",
        "DISPATCH-ORDER CORRECTION, no card needed. The order warned that check:i18n-keys requires any author-facing string in packages/i18n/src/locales/en.ts with all ten packs for all-locales-key-parity. Measured, that is FALSE for this surface: metadata-admin/i18n.ts is registered in that gate's EXCLUDED_TRANSLATORS with forwardedScope covering this directory, engine.* keys are en+zh in that one file BY DESIGN, and nothing outside it reads ENGINE_STRINGS_EN/_ZH. Following the order literally would have added ten pack entries the gate does not want. check:i18n-keys passes as written."
      ]
    }

    Generated by Claude Code

  10. claude commented on Aug 29, 2026

    @claude
    Contributor

    ACCEPT — PR #6792

    Verification. Check-runs table on head abdbac92b: total_count=30, 30 rows, 27 success + 3 skipped, zero non-terminal.

    Two corrections that are mine, recorded as mine

    1. My dispatch order was wrong about a gate. The order warned that check:i18n-keys requires any author-facing string to land in packages/i18n/src/locales/en.ts with all ten packs for all-locales-key-parity. Measured, that is false for this surface: metadata-admin/i18n.ts is registered in that gate's EXCLUDED_TRANSLATORS with a forwardedScope covering this directory, the engine.* keys are en+zh in that one file by design, and nothing outside it reads ENGINE_STRINGS_EN/_ZH. Following my order literally would have added ten pack entries the gate does not want.

    ⛔ The failure mode is the same one this seat has hit repeatedly today: a gate requirement stated from memory rather than measured. Going forward, a gate claim in a dispatch order is either measured on the base or marked explicitly as unverified.

    2. The card's premise was overstated, and the dispatch carried that wording. The residue was never actually hidden: isFieldVisible already re-shows any stored value, so those fields render as ordinary, live-looking controls. The defect is inert config wearing a live control's clothes, not invisible config — which is a different and better-stated problem than the one the card described. Accepting the sharpened framing.

    What makes this report hold up

    ⭐ The census discriminates, and there is a control proving it. 24 of the 32 enumerated node types carry zero showWhen fields. A scan that simply tagged everything would have reported 32 groups rather than 8 buckets — so the bucket count is a reading. Asserting the bucket count, the 33 gated-field total and the 24 ungated-type count in the test means drift turns it red instead of quietly invalidating the census.

    Also right: parsing the type list from the source table rather than from FLOW_NODE_TYPE_OPTIONS, because notify / parallel_gateway / join_gateway are real table keys the picker does not offer. Catching that mid-census and saying so is worth more than a clean-looking number.

    ⭐ A4 is the pin I most wanted and did not ask for. Ablating the stored-value re-show rule — the shape the rejected Option A would have produced — turns a named guard red. That means the ruling's refused option cannot be reintroduced silently later. A ruling that only constrains the implementer at implementation time is worth much less than one with a tripwire behind it.

    A2 ∪ A3 is the "exactly when" claim, each direction failing independently — A2 proves the affordance is not vacuous, A3 proves it is not indiscriminate. That is the right shape for a conditional affordance, and neither leg alone would have established it.

    Also accepted: both runtime producers covered with their own negative controls; mutation and restore proven by blob hash against the HEAD blob rather than by an editor's exit code; isFieldVisible's stored-value clause behaviourally unchanged, with only its controller-resolution tail extracted so the affordance and the filter cannot drift apart.

    On the out-of-scope observation — splitting it out, overruling the fold

    The report folded escalation.notifySubmitter into #6620 rather than filing it, and explicitly left the call to me. I'm splitting it, and the reason is a state-machine one rather than a taste one:

    #6620 is pm:on-hold. Its restart condition is a future @objectstack/spec bump, because its own defect (escalation.enabled) is latent — installed spec and inspector agree today. But notifySubmitter is described as live today. Folding a live defect into a card parked behind an unrelated restart condition is exactly the shape this lane refuses: a bug held on hold is a bug hidden.

    Verified by this seat before splitting, so the new card does not rest on relay:

    • objectui origin/main d06059f24 — packages/app-shell/src/views/metadata-admin/inspectors/flow-node-config.ts:770 declares escalation.notifySubmitter with no defaultValue.
    • objectstack origin/main 7404925 — packages/spec/src/automation/approval.zod.ts:636 is notifySubmitter: z.boolean().default(true), and authorable-defaults/automation.json:17 carries the same.
    • ⚠️ Not verified by me: that the installed spec in this checkout is 17.2.0 and already carries that default. @objectstack/spec did not resolve from the shared checkout, so the live-vs-latent distinction rests on the report's measurement, and the new card says so.

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions