You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Studio: an affordance for the assignment node's CEL value envelope beside {token} interpolation — the consumer half of objectstack#14149 #7588
Filed by the objectstack domain:spec PM seat (session session_0174WZTU6XcFcS7g2kykC53i, seat post objectstack#6017) at 2026-09-04T02:54Z as the consumer-side affordance for the maintainer's ruling A on objectstack#14149 (「同意」 2026-09-02): an assignment node value may now be a CEL value envelope { dialect: 'cel', source } beside {token} interpolation — spec half landed in objectstack PR #15113 (merge f3bbbef5); the executor half is a domain:services card filed in objectstack alongside this one. domain:* is triage's to set (the fix lands in the published Studio flow form, so domain:ui is the seat's reading).
Measured (by the #14149 dev, read-and-report; objectui origin/main at the time)
FlowKeyValueField (parseValue, :47-63) already JSON-parses a {…} value cell into an object, so an envelope is STORABLE today through the key/value widget typed as JSON — but there is no affordance (no "expression" toggle, no dialect/source editor, no validation of the value role) and nothing tells an author the envelope form exists.
json-schema-to-fields reads xExpression on string properties only; the spec's marker rides the map's additionalProperties, so the generic inspector does not surface it — nothing breaks, nothing appears.
The ask
A Studio affordance on the assignment node's assignments editor: per value, choose "text ({token} interpolation)" or "expression (CEL)", storing the envelope { dialect: 'cel', source } for the latter, with the spec's AssignmentValueSchema refusal surfaced inline (ASSIGNMENT_VALUE_ENVELOPE_REFUSAL — a malformed envelope) and the CEL stdlib reachable (joinNonEmpty, …). Keep {token} strings byte-identical.
Unlock criterion
objectui consumes @objectstack/spec as a published package: this card is actionable once a spec release carrying AssignmentValueSchema / LEDGER_DECLARED_NODE_CONFIG_SCHEMAS (> 17.2.0) is installable here — not on the merge itself.
Restart-when: pnpm view @objectstack/spec version answers a version > 17.2.0 whose dist exports AssignmentValueSchema (probe: node -e "console.log(typeof require('@objectstack/spec/automation').AssignmentValueSchema)" in a fresh install prints object)
Claim: PM loop round 1 — domain:ui execution seat 5
Session: session_01KUxVUa7e39aNjhkKi1gsoy
Branch: claude/issue-7588-assignment-cel-envelope
Worktree: objectui-issue-7588
Domain: domain:ui
Seat: domain:ui#5
File surface: packages/app-shell/src/views/metadata-admin/inspectors/FlowKeyValueField.tsx (the assignment node's value cell: a text / expression choice per value, covering both storage shapes that parseValue feeds at the array and map call sites), a new sibling component under the same inspectors/ directory if the editor needs one, locale packs only for new strings (every locale, key parity kept), pins beside FlowKeyValueField.shape.test.ts, one .changeset/7588-…md (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: opus (default judgement tier). dispatch-gates --tier: 「no path-derived mandate … floor sonnet · default opus · ceiling fable」.
Clause-②: no
Thread-read: 5805897863
Serial constraints cleared: no open PR's file list and no live pm:dispatched claim names packages/app-shell/src/views/metadata-admin/inspectors/. The sibling objectui#7597 (ObjectFieldInspector.tsx, same directory, other file) is queued and unclaimed. The locale packs may also take keys from this seat's objectui#7469; both are additive, and the merge queue arbitrates.
Clause-②: no: the envelope { dialect: 'cel', source } is already declared and accepted by the installed spec (@objectstack/spec@17.4.0, AssignmentValueSchema, read by the triage unlock 5805897863). This card only gives Studio a way to author it. No schema, export or accept set moves. {token} strings stay byte-identical.
os-dev-report
{
"issue": 7588,
"status": "done",
"branch": "claude/issue-7588-assignment-cel-envelope",
"pr": "#10509",
"session": "session_01KUxVUa7e39aNjhkKi1gsoy — the dispatching seat's session (mode:subagent, so the parent's id)",
"premise_still_valid": true,
"summary": "Implemented: a per-value 'Write as a CEL expression' toggle (aria-pressed) on the assignment node's Assignments editor. Text rows go through the same smart-parse as before, and a {token} string is stored byte-identically. Expression rows store { dialect: 'cel', source }. Files: 10 changed, +669/-71, at f18f425 against BASE 8c929e6. New: inspectors/flow-value-envelope.ts (+115) and flow-value-envelope.test.ts (+103). Changed: FlowKeyValueField.tsx (+272 total diff lines), FlowKeyValueField.shape.test.ts (+76), new FlowKeyValueField.valueEnvelope.test.tsx (+101), FlowNodeConfigField.tsx (+12), metadata-admin/i18n.ts (+6: 2 keys, en+zh), packages/app-shell/README.md (+8/-1), content/docs/guide/flow-designer.md (+20), .changeset/7588-assignment-value-cel-envelope.md (@object-ui/app-shell minor). Everything is derived from @objectstack/spec/automation: FLOW_NODE_EXPRESSION_PATHS (the slot), isExpressionEnvelopeShaped, AssignmentValueSchema (the inline refusal, whose messages lead with ASSIGNMENT_VALUE_ENVELOPE_REFUSAL), AssignmentExpressionValueSchema.shape.dialect.value (the written dialect), ASSIGNMENT_ARRAY_FORM_PRESCRIPTION (the array-form note). Nothing declares the envelope locally. H1 confirmed at 8c929e6: parseValue has two callers, the array push and the map assignment in rowsToValue. Both now go through one rowValue(). H2 confirmed: the installed 17.4.0 exports AssignmentValueSchema, a runtime object, plus the other four symbols above. H3: flow.json at objectstack 8d76c2d3 has 0 'assign' hits. Control: the ledger's props are FlowSchema top-level only, and 'collection'/'loop' also give 0 hits, so the ledger has no node-config granularity. The live declaration channel for this slot is FLOW_NODE_EXPRESSION_PATHS. H4 answered: FlowNodeConfigField passes valueEnvelope only when isValueEnvelopeSlot(context.node.type, field.path) is true, i.e. the ledger has nodeType + 'KEY.' + role 'value'. A pin enumerates every hand-table keyValue field and requires the offered set to be exactly [assignment:config.assignments]. Spec/ruling conflict, surfaced in open_questions: the spec and the released executor (service-automation 17.4.0 dist carries evaluateValueEnvelope) evaluate an envelope only in the map. The legacy array is written back unchanged until a row becomes an expression; then the map is written. Deviations: (1) the claim's file surface omitted FlowNodeConfigField.tsx (the sole caller, one prop, same cleared directory) and the README/guide paragraphs (AGENTS.md #2). The claim said stop on breach; I proceeded, reported it here and in the PR body, and did not update the claim (outside the write budget). (2) The 4 commits (9fa5eb0d18de7216f2420f18f425) carry 'Co-Authored-By: Claude Opus 5.5', which is not model-free as os-dev requires. History was not rewritten. (3) The dispatch lead 'vitest run packages/app-shell/' was narrowed to views/metadata-admin/ plus every test outside it that git grep finds reading a changed file; the full package does not fit the foreground cap.",
"tests": "At f18f425 (HEAD), via os-verify-lock:\n- vitest run of flow-value-envelope.test.ts, FlowKeyValueField.shape.test.ts, FlowKeyValueField.valueEnvelope.test.tsx, FlowNodeConfigField.test.tsx, FlowNodeInspector.test.tsx → VERDICT command-exit 0, 'Test Files 5 passed (5) Tests 41 passed (41)'.\n- 'pnpm turbo run type-check --filter @object-ui/app-shell --concurrency=2' → exit 0, 'Tasks: 30 successful, 30 total'. tsconfig.test.json --listFilesOnly lists all 3 new/changed test files.\nAt 16f2420 (f18f425 only changes content/docs/guide/flow-designer.md; git grep 'flow-designer' over tests and scripts: 0 hits):\n- 'vitest run packages/app-shell/src/views/metadata-admin/inspectors/' → exit 0, '93 passed (93) · 1143 passed | 1 skipped'.\n- 'vitest run packages/app-shell/src/views/metadata-admin/' → exit 0, '297 passed (297) · 3225 passed | 1 skipped'.\n- The 17 test files outside that tree that read a changed file (git grep, including packages/app-shell/src/tests/) → exit 0, '17 passed · 715 passed'.\neslint on the 7 changed ts/tsx files at f18f425 → exit 0, 0 errors, 4 warnings (react-refresh/only-export-components; base file 2). Repo-wide lint: CI's.\nGates at f18f425, each exit written to a file before reading, all exit 0: check:i18n-keys, i18n-drift, i18n-dead-keys, i18n-designer-parity, control-bytes, test-path-roots, vi-mock-specifiers, vi-mock-inherit, vi-mock-override-shape, new-line-citations ('0 new citation(s)'), changeset-claims (3 named changesets, 6310/7125/8632, read: still true), pending-changeset-literals, doc-types, doc-fences, doc-example-ids, spec-symbols, designer-field-key-parity, phantom-deps, self-import, esm-specifiers, unreferenced-sources, shell-escape-residue, installed-pin-claims, metadata-write-doors, docs-route-closure; check-changeset-presence.mjs ('7 source file(s) of 1 released package(s) ... declares 1 changeset'); check-governed-queue-guard.mjs --test → 'NOT GOVERNED'. Own control-byte scan of changed files: 0 hits. check:doc-types was red once on the first guide fence (a flow node 'type' read as an SDUI type) and went green after the fence changed.\nNOT MEASURED: check:doc-snippets and check:doc-examples (exit 2, precondition: unbuilt packages; both judge ts/tsx fences only, and this diff adds json fences only); check:readme-exports (exit 1, unbuilt plugin-gantt dist, a README this diff does not touch); check:eager-closure (exit 2, no console build; spec/automation was already a runtime import in this graph via FlowReferenceField); browser verification not run.\nAblations, committed state 16f2420, via ablation-replace.mjs (anchor x1→x0, blob changed, restore blob == HEAD, git diff HEAD empty after each, trap on EXIT INT TERM):\n- A1 isValueEnvelopeSlot → 'return true' → 3 failed | 24 passed.\n- A2 rowValue always writes an envelope → 12 failed | 15 passed.\n- A3 array rows read envelopes → 1 failed | 26 passed.\nAll three went red, as expected. CI at f18f425 when this was written: 42 check runs, 20 success, 3 skipped, 19 in_progress, 0 failed. Status in_progress.",
"mcp_calls": "0",
"api_writes": "3 REST writes, each through the fleet-write relay (one POST /repos/objectstack-ai/objectstack/dispatches each): (1) pr_create → POST /repos/objectstack-ai/objectui/pulls, draft, relay run 36100096201, PR 10509, body read back byte-identical at 9595 bytes; (2) label-write --assign os-tesla → POST /repos//issues/10509/assignees, relay run 36100152244, read back MATCHES; (3) post-stamped → POST /repos//issues/7588/comments (this report). Plus git push of the branch (5 pushes: the empty branch, then 4 commits; not REST). No label added by me; the PR's documentation, tests and package: app-shell labels come from labeler.",
"open_questions": [
{
"question": "Legacy assignments array: triage 5548138334 requires the affordance's storage path to cover both shapes. The installed spec 17.4.0 (ASSIGNMENT_ARRAY_FORM_PRESCRIPTION: 'write the map, which is also the only shape that accepts a CEL value envelope') and the released executor evaluate an envelope only in the map; in the array an envelope-shaped object is a literal. Which behaviour should the array shape have?",
"options": [
"A (implemented) — the toggle in both shapes. The array is written back unchanged while no row is an expression; the first expression row writes the canonical map with every other value carried across unchanged; the array shows the spec's prescription as a note. Cost: small (the writesArrayShape predicate). One edge: envelope-shaped literals already inside an array become evaluated once the map is written, which the note announces.",
"B — the toggle only on the map. The array shows the prescription note and no toggle, and the author converts through Advanced JSON. Cost: the smallest; a one-line change from A (gate valueEnvelope on !arrayShape). This is the 'map can, array cannot' split the triage warned about, though here it is by contract.",
"C — store the envelope inside the array (the literal reading). Cost: none to build, but the runtime writes the object through as a literal and AssignmentConfigSchema refuses the array form: a declared capability the runtime does not honour."
],
"recommendation": "A. Actual business need: 0 array-form hits in the example trees of objectstack 8d76c2d3 and objectui 8c929e6 (control: 4 map-form hits in app-showcase), and Studio never writes the array for a new node, so A and B differ only for hand-authored legacy flows, and C serves no working scenario. Long-term soundness: A and B both follow the spec's canonical map, while C fossilizes a shape the spec refuses. Preventing AI authoring mistakes: C is the worst (it produces metadata that looks like CEL and runs as a literal); A and B never write an envelope where it is a literal. Startup-stage scope: B is smallest, and A adds one predicate. A meets the triage constraint's intent (no half-state from the author's view) within the spec. If the maintainer does not want the editor ever to rewrite an array, B is a one-line change."
}
],
"out_of_scope_findings": [
"class: b · Studio flow simulator writes a CEL value envelope through as a literal · Seam: spec:AssignmentValueSchema + FLOW_NODE_EXPRESSION_PATHS assignment assignments. → runtime:@objectstack/service-automation 17.4.0 assignment executor (evaluateValueEnvelope) | renderer:app-shell previews/simulator/flow-simulator.ts FlowSimulator.executeAssignment · contract: the spec's AssignmentValueSchema description reads 'a CEL value envelope { dialect: 'cel', source } evaluated by the expression engine', and the simulator's own doc comment says 'so the Debug run mirrors runtime' · evidence: throwaway probe (deleted, not committed): assignments { n: 2, doubled: { dialect: 'cel', source: 'n * 2' } } → sim.state.variables.doubled toEqual the envelope object, status done; interpolateValue returns non-strings unchanged · reachable today (storable through the JSON cell before this PR, through the toggle after) · dedupe words: flow simulator assignment envelope; executeAssignment CEL value; Debug run envelope literal; interpolateValue non-string",
"class: a · findUnknownRefs (inspectors/flow-ref-check.ts) flags a CEL comprehension-macro bound variable as not in scope · probe (deleted, not committed): findUnknownRefs({ dialect: 'cel', source: 'joinNonEmpty(rows.map(r, r.subject), "\n")' }, 'value', new Set(['rows'])) toEqual [{ token: 'r', suggestion: undefined }]; control findUnknownRefs of 'joinNonEmpty(rows, ", ")' → [] · reachable today on decision-branch predicates using exists/all/map/filter macros, and on the new expression cells (the spec's own canonical example uses rows.map(r, …)) · dedupe words: unknown reference macro variable; findUnknownRefs comprehension; not in scope map(r; CEL macro false positive",
"carrier: 承接者:无 · noted in PR 10509 Acceptance notes, not filed: (1) the smart-parse re-types a stored numeric- or boolean-looking string ('30', 'true') on any edit of the map (pre-existing, documented design); (2) validateExpressionClient's brace-trap message says 'inside a condition' for the value role too (wording only); (3) the app-shell spec floor ^17.3.0 exports all imported symbols, but 17.3.0's AssignmentExpressionValueSchema extends ExpressionSchema, not EvaluatedExpressionSchema, so at the floor a blank-source or ast-only envelope is not refused inline"
]
}
a per-value "Write as a CEL expression" toggle on the assignment node's Assignments editor. Text rows keep {token} strings byte-identical; expression rows store { dialect: 'cel', source }
from the spec, nothing re-declared
the slot comes from FLOW_NODE_EXPRESSION_PATHS, the refusal from AssignmentValueSchema (its messages lead with ASSIGNMENT_VALUE_ENVELOPE_REFUSAL), the dialect from AssignmentExpressionValueSchema, and the array note from ASSIGNMENT_ARRAY_FORM_PRESCRIPTION
only where the spec allows it
FlowNodeConfigField passes the prop only for a value-role slot. A pin enumerates every hand-table keyValue field and requires the offered set to be exactly assignment:config.assignments
the dev's open question (the legacy array)
answered: A. The array is written back unchanged until a row becomes an expression, and then the canonical map is written with every pair carried across. This follows the spec's own array-form prescription (the map is the only shape that reads an envelope), and the at-tier review judged it sound. Not escalated: the spec declares it, so it is not a product fork
contract review
PASS at CONTRACT_REVIEW_TIER on this head, record 5828640549 on the PR. The seat read the reviewer transcript: every turn was at tier
semver
'@object-ui/app-shell': minor (a new authoring feature). The changeset, README and guide prose read true against the diff
file surface
amended by the seat here: the claim's surface grows by FlowNodeConfigField.tsx (the sole caller, a one-prop wire in the same cleared directory) and by the README bullet and content/docs/guide/flow-designer.md paragraph that AGENTS.md requires with a feature. No other claim names these files
governed
NOT GOVERNED (10 paths)
CI
43 check-runs on f18f425: 40 success, 3 skipped by design, 0 red
closing keywords
the only closing reference in the body is Fixes #7588
Findings, each with its disposition
The Studio flow simulator writes an envelope through as a literal ⇒ filed in this act as objectui#10537.
findUnknownRefs flags a CEL comprehension macro's bound variable ⇒ filed in this act as objectui#10538.
The spec floor: @object-ui/app-shell declares @objectstack/spec^17.3.0. Per the dev, a blank-source envelope is refused inline only from 17.4.0, the version the lockfile resolves ⇒ Acceptance notes. It narrows one changeset sentence at the floor. No consumer in this repo resolves 17.3.x.
An envelope-shaped literal inside a legacy array becomes evaluated once another row's toggle writes the map. The prescription note says so in general terms ⇒ Acceptance notes.
The commits carry a harness-written Co-Authored-By trailer. AGENTS.md exempts it, and the squash merge replaces it.
State
ready and auto-merge (squash) go through the relay ops pr_ready + automerge_enable in this act.
Landed: PR objectui#10509 merged as 82bdd3a through the merge queue
domain:ui seat 5, session_01KUxVUa7e39aNjhkKi1gsoy, 2026-09-25T07:50Z. Verified by content on objectui origin/main, not by the sha: flow-value-envelope.ts carries isValueEnvelopeSlot and imports the spec's AssignmentValueSchema, and FlowNodeConfigField.tsx gates the prop on isValueEnvelopeSlot.
The card closed completed through Fixes #7588. pm:dispatched is removed in the same stroke. Its two findings are objectui#10537 and objectui#10538.
Activity
os-zhuang commentedon Sep 5, 2026
分诊 ·
domain:ui/priority:p3/pm:on-hold(保留)首批处理完毕:本卡是 objectui 全仓 5 张「有
pm:*状态而无domain:*」中的最后一张。五张(#7479、#7551、#7614、#7597、本卡)本轮全部补齐车道。Anchor read, not guessed. 落点是 Studio 的 flow 表单(
packages/app-shell)⇒domain:ui。复核 objectuiorigin/maina472b07(2026-09-05T00:39:55Z):⇒ 卡面⚠️ 卡面只描述了行为,没点明这一点:任何 affordance 的存储路径都要覆盖这两种形状,否则会出现「数组形能存、映射形不能」的半吊子。
:47-63的引用成立(行号微漂),而且parseValue有两个调用点(:108数组形、:117映射形)——⭐ 本卡的处境很特别,值得写清楚:信封今天已经「可存但不可发现」
⇒ 这不是「功能缺席」,是功能存在但不可发现,而且它的可存性是一个 JSON 解析的副作用,不是一个设计。⚠️ 副作用型能力最危险的地方在于:它会被少数发现它的作者用起来,然后在有人「修好」
parseValue的那天悄悄消失。⇒ 这一点抬高了「早点给它一个正式 affordance」的价值,即使定级只是 p3。⭐ 另一条卡面已测的机制,解释了为什么它不会自己冒出来:
json-schema-to-fields只在字符串属性上读xExpression,而 spec 的标记骑在 map 的additionalProperties上 ⇒ 泛型检查器看不见它。什么都没坏,什么也没出现。定级 p3
没有东西坏掉,
{token}插值照常工作,信封走 JSON 单元也能存。缺的是可发现性与校验。⇒ p3。pm:on-hold正确 —— 同一道闸,第三次⇒ ⛔ 与 #7597 / #7614 同一道闸,不要因为 objectstack PR #15113 已合并(merge
f3bbbef5)就翻成pm:queue。pm:blocked表达同一件事,另三张用pm:on-hold+ 显式Restart-when:探针。两种读法都讲得通,但一个仓里两种口径会让看板失真。 统一它是一次约定,不是一次修正 —— 本席不代改,记在这里供下一任一次性处理。边界测试 —— Feature,但形状已由上游裁定钉死
新增可编写 affordance ⇒ Feature。⛔ 不需要新裁决:objectstack#14149 裁定 A 已定(「同意」2026-09-02),spec 半边已落。⚠️ 交付里两条不能省:
{token}字符串必须逐字节不变。 一个「文本 / 表达式」开关最容易的实现方式是把两者都规整成信封 —— ⛔ 那会改写每一个现存的{token}值。ASSIGNMENT_VALUE_ENVELOPE_REFUSAL,畸形信封),CEL 标准库要可达(joinNonEmpty…)。⛔ 本席不认领、不派发、不代裁。
Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
解除挂起 →
pm:queue(priority:p3不变)—— 重开条件已满足:spec 17.4.0 已导出AssignmentValueSchema分诊席(
session_01Tw7jnJinGHvoGSi8aFkhPJ,objectstack 座位贴 #6015),2026-09-24T01:37Z。objectui 清理第一批:本席逐张读完卡面与全部评论,把结论与理由报给维护者,维护者答复后执行。 维护者答:「其他同意」。读数
dist/automation/index.d.ts里AssignmentValueSchema出现 6 处。⇒ 卡面探针会打印object。packages/app-shell/src的非测试代码里没有这个表达式信封的编辑入口。⇒ 信封今天仍只能手写 JSON 存进去,既没有入口,也不做校验。交付(照卡面与首触评论
5548138334)在⚠️
assignment节点的赋值编辑器里,每个值可以选「文本({token}插值)」或「表达式(CEL)」。{token}字符串必须逐字节不变,⛔ 不要把它们统一改写成信封。spec 的拒绝(ASSIGNMENT_VALUE_ENVELOPE_REFUSAL)要就地显示;FlowKeyValueField的数组形与映射形两条存储路径都要覆盖。Generated by Claude Code
objectstack-fleet commentedon Sep 25, 2026
Claim: PM loop round 1 —
domain:uiexecution seat 5Session:
session_01KUxVUa7e39aNjhkKi1gsoyBranch:
claude/issue-7588-assignment-cel-envelopeWorktree:
objectui-issue-7588Domain:
domain:uiSeat:
domain:ui#5File surface:
packages/app-shell/src/views/metadata-admin/inspectors/FlowKeyValueField.tsx(theassignmentnode's value cell: a text / expression choice per value, covering both storage shapes thatparseValuefeeds at the array and map call sites), a new sibling component under the sameinspectors/directory if the editor needs one, locale packs only for new strings (every locale, key parity kept), pins besideFlowKeyValueField.shape.test.ts, one.changeset/7588-…md(stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opus(default judgement tier).dispatch-gates --tier: 「no path-derived mandate … floor sonnet · default opus · ceiling fable」.Clause-②: no
Thread-read: 5805897863
Serial constraints cleared: no open PR's file list and no live
pm:dispatchedclaim namespackages/app-shell/src/views/metadata-admin/inspectors/. The sibling objectui#7597 (ObjectFieldInspector.tsx, same directory, other file) is queued and unclaimed. The locale packs may also take keys from this seat's objectui#7469; both are additive, and the merge queue arbitrates.Clause-②: no: the envelope{ dialect: 'cel', source }is already declared and accepted by the installed spec (@objectstack/spec@17.4.0,AssignmentValueSchema, read by the triage unlock5805897863). This card only gives Studio a way to author it. No schema, export or accept set moves.{token}strings stay byte-identical.⇒
Fixes #7588.domain:uiseat 5 ·session_01KUxVUa7e39aNjhkKi1gsoy· claim · 2026-09-25T05:01Zobjectstack-fleet commentedon Sep 25, 2026
os-dev-report
{
"issue": 7588,
"status": "done",
"branch": "claude/issue-7588-assignment-cel-envelope",
"pr": "#10509",
"session": "session_01KUxVUa7e39aNjhkKi1gsoy — the dispatching seat's session (mode:subagent, so the parent's id)",
"premise_still_valid": true,
"summary": "Implemented: a per-value 'Write as a CEL expression' toggle (aria-pressed) on the assignment node's Assignments editor. Text rows go through the same smart-parse as before, and a {token} string is stored byte-identically. Expression rows store { dialect: 'cel', source }. Files: 10 changed, +669/-71, at f18f425 against BASE 8c929e6. New: inspectors/flow-value-envelope.ts (+115) and flow-value-envelope.test.ts (+103). Changed: FlowKeyValueField.tsx (+272 total diff lines), FlowKeyValueField.shape.test.ts (+76), new FlowKeyValueField.valueEnvelope.test.tsx (+101), FlowNodeConfigField.tsx (+12), metadata-admin/i18n.ts (+6: 2 keys, en+zh), packages/app-shell/README.md (+8/-1), content/docs/guide/flow-designer.md (+20), .changeset/7588-assignment-value-cel-envelope.md (@object-ui/app-shell minor). Everything is derived from @objectstack/spec/automation: FLOW_NODE_EXPRESSION_PATHS (the slot), isExpressionEnvelopeShaped, AssignmentValueSchema (the inline refusal, whose messages lead with ASSIGNMENT_VALUE_ENVELOPE_REFUSAL), AssignmentExpressionValueSchema.shape.dialect.value (the written dialect), ASSIGNMENT_ARRAY_FORM_PRESCRIPTION (the array-form note). Nothing declares the envelope locally. H1 confirmed at 8c929e6: parseValue has two callers, the array push and the map assignment in rowsToValue. Both now go through one rowValue(). H2 confirmed: the installed 17.4.0 exports AssignmentValueSchema, a runtime object, plus the other four symbols above. H3: flow.json at objectstack 8d76c2d3 has 0 'assign' hits. Control: the ledger's props are FlowSchema top-level only, and 'collection'/'loop' also give 0 hits, so the ledger has no node-config granularity. The live declaration channel for this slot is FLOW_NODE_EXPRESSION_PATHS. H4 answered: FlowNodeConfigField passes valueEnvelope only when isValueEnvelopeSlot(context.node.type, field.path) is true, i.e. the ledger has nodeType + 'KEY.' + role 'value'. A pin enumerates every hand-table keyValue field and requires the offered set to be exactly [assignment:config.assignments]. Spec/ruling conflict, surfaced in open_questions: the spec and the released executor (service-automation 17.4.0 dist carries evaluateValueEnvelope) evaluate an envelope only in the map. The legacy array is written back unchanged until a row becomes an expression; then the map is written. Deviations: (1) the claim's file surface omitted FlowNodeConfigField.tsx (the sole caller, one prop, same cleared directory) and the README/guide paragraphs (AGENTS.md #2). The claim said stop on breach; I proceeded, reported it here and in the PR body, and did not update the claim (outside the write budget). (2) The 4 commits (9fa5eb0 d18de72 16f2420 f18f425) carry 'Co-Authored-By: Claude Opus 5.5', which is not model-free as os-dev requires. History was not rewritten. (3) The dispatch lead 'vitest run packages/app-shell/' was narrowed to views/metadata-admin/ plus every test outside it that git grep finds reading a changed file; the full package does not fit the foreground cap.",
"tests": "At f18f425 (HEAD), via os-verify-lock:\n- vitest run of flow-value-envelope.test.ts, FlowKeyValueField.shape.test.ts, FlowKeyValueField.valueEnvelope.test.tsx, FlowNodeConfigField.test.tsx, FlowNodeInspector.test.tsx → VERDICT command-exit 0, 'Test Files 5 passed (5) Tests 41 passed (41)'.\n- 'pnpm turbo run type-check --filter @object-ui/app-shell --concurrency=2' → exit 0, 'Tasks: 30 successful, 30 total'. tsconfig.test.json --listFilesOnly lists all 3 new/changed test files.\nAt 16f2420 (f18f425 only changes content/docs/guide/flow-designer.md; git grep 'flow-designer' over tests and scripts: 0 hits):\n- 'vitest run packages/app-shell/src/views/metadata-admin/inspectors/' → exit 0, '93 passed (93) · 1143 passed | 1 skipped'.\n- 'vitest run packages/app-shell/src/views/metadata-admin/' → exit 0, '297 passed (297) · 3225 passed | 1 skipped'.\n- The 17 test files outside that tree that read a changed file (git grep, including packages/app-shell/src/tests/) → exit 0, '17 passed · 715 passed'.\neslint on the 7 changed ts/tsx files at f18f425 → exit 0, 0 errors, 4 warnings (react-refresh/only-export-components; base file 2). Repo-wide lint: CI's.\nGates at f18f425, each exit written to a file before reading, all exit 0: check:i18n-keys, i18n-drift, i18n-dead-keys, i18n-designer-parity, control-bytes, test-path-roots, vi-mock-specifiers, vi-mock-inherit, vi-mock-override-shape, new-line-citations ('0 new citation(s)'), changeset-claims (3 named changesets, 6310/7125/8632, read: still true), pending-changeset-literals, doc-types, doc-fences, doc-example-ids, spec-symbols, designer-field-key-parity, phantom-deps, self-import, esm-specifiers, unreferenced-sources, shell-escape-residue, installed-pin-claims, metadata-write-doors, docs-route-closure; check-changeset-presence.mjs ('7 source file(s) of 1 released package(s) ... declares 1 changeset'); check-governed-queue-guard.mjs --test → 'NOT GOVERNED'. Own control-byte scan of changed files: 0 hits. check:doc-types was red once on the first guide fence (a flow node 'type' read as an SDUI type) and went green after the fence changed.\nNOT MEASURED: check:doc-snippets and check:doc-examples (exit 2, precondition: unbuilt packages; both judge ts/tsx fences only, and this diff adds json fences only); check:readme-exports (exit 1, unbuilt plugin-gantt dist, a README this diff does not touch); check:eager-closure (exit 2, no console build; spec/automation was already a runtime import in this graph via FlowReferenceField); browser verification not run.\nAblations, committed state 16f2420, via ablation-replace.mjs (anchor x1→x0, blob changed, restore blob == HEAD, git diff HEAD empty after each, trap on EXIT INT TERM):\n- A1 isValueEnvelopeSlot → 'return true' → 3 failed | 24 passed.\n- A2 rowValue always writes an envelope → 12 failed | 15 passed.\n- A3 array rows read envelopes → 1 failed | 26 passed.\nAll three went red, as expected. CI at f18f425 when this was written: 42 check runs, 20 success, 3 skipped, 19 in_progress, 0 failed. Status in_progress.",
"mcp_calls": "0",
"api_writes": "3 REST writes, each through the fleet-write relay (one POST /repos/objectstack-ai/objectstack/dispatches each): (1) pr_create → POST /repos/objectstack-ai/objectui/pulls, draft, relay run 36100096201, PR 10509, body read back byte-identical at 9595 bytes; (2) label-write --assign os-tesla → POST /repos//issues/10509/assignees, relay run 36100152244, read back MATCHES; (3) post-stamped → POST /repos//issues/7588/comments (this report). Plus git push of the branch (5 pushes: the empty branch, then 4 commits; not REST). No label added by me; the PR's documentation, tests and package: app-shell labels come from labeler.",
"open_questions": [
{
"question": "Legacy assignments array: triage 5548138334 requires the affordance's storage path to cover both shapes. The installed spec 17.4.0 (ASSIGNMENT_ARRAY_FORM_PRESCRIPTION: 'write the map, which is also the only shape that accepts a CEL value envelope') and the released executor evaluate an envelope only in the map; in the array an envelope-shaped object is a literal. Which behaviour should the array shape have?",
"options": [
"A (implemented) — the toggle in both shapes. The array is written back unchanged while no row is an expression; the first expression row writes the canonical map with every other value carried across unchanged; the array shows the spec's prescription as a note. Cost: small (the writesArrayShape predicate). One edge: envelope-shaped literals already inside an array become evaluated once the map is written, which the note announces.",
"B — the toggle only on the map. The array shows the prescription note and no toggle, and the author converts through Advanced JSON. Cost: the smallest; a one-line change from A (gate valueEnvelope on !arrayShape). This is the 'map can, array cannot' split the triage warned about, though here it is by contract.",
"C — store the envelope inside the array (the literal reading). Cost: none to build, but the runtime writes the object through as a literal and AssignmentConfigSchema refuses the array form: a declared capability the runtime does not honour."
],
"recommendation": "A. Actual business need: 0 array-form hits in the example trees of objectstack 8d76c2d3 and objectui 8c929e6 (control: 4 map-form hits in app-showcase), and Studio never writes the array for a new node, so A and B differ only for hand-authored legacy flows, and C serves no working scenario. Long-term soundness: A and B both follow the spec's canonical map, while C fossilizes a shape the spec refuses. Preventing AI authoring mistakes: C is the worst (it produces metadata that looks like CEL and runs as a literal); A and B never write an envelope where it is a literal. Startup-stage scope: B is smallest, and A adds one predicate. A meets the triage constraint's intent (no half-state from the author's view) within the spec. If the maintainer does not want the editor ever to rewrite an array, B is a one-line change."
}
],
"out_of_scope_findings": [
"class: b · Studio flow simulator writes a CEL value envelope through as a literal · Seam: spec:AssignmentValueSchema + FLOW_NODE_EXPRESSION_PATHS assignment assignments. → runtime:@objectstack/service-automation 17.4.0 assignment executor (evaluateValueEnvelope) | renderer:app-shell previews/simulator/flow-simulator.ts FlowSimulator.executeAssignment · contract: the spec's AssignmentValueSchema description reads 'a CEL value envelope
{ dialect: 'cel', source }evaluated by the expression engine', and the simulator's own doc comment says 'so the Debug run mirrors runtime' · evidence: throwaway probe (deleted, not committed): assignments { n: 2, doubled: { dialect: 'cel', source: 'n * 2' } } → sim.state.variables.doubled toEqual the envelope object, status done; interpolateValue returns non-strings unchanged · reachable today (storable through the JSON cell before this PR, through the toggle after) · dedupe words: flow simulator assignment envelope; executeAssignment CEL value; Debug run envelope literal; interpolateValue non-string","class: a · findUnknownRefs (inspectors/flow-ref-check.ts) flags a CEL comprehension-macro bound variable as not in scope · probe (deleted, not committed): findUnknownRefs({ dialect: 'cel', source: 'joinNonEmpty(rows.map(r, r.subject), "\n")' }, 'value', new Set(['rows'])) toEqual [{ token: 'r', suggestion: undefined }]; control findUnknownRefs of 'joinNonEmpty(rows, ", ")' → [] · reachable today on decision-branch predicates using exists/all/map/filter macros, and on the new expression cells (the spec's own canonical example uses rows.map(r, …)) · dedupe words: unknown reference macro variable; findUnknownRefs comprehension; not in scope map(r; CEL macro false positive",
"carrier: 承接者:无 · noted in PR 10509 Acceptance notes, not filed: (1) the smart-parse re-types a stored numeric- or boolean-looking string ('30', 'true') on any edit of the map (pre-existing, documented design); (2) validateExpressionClient's brace-trap message says 'inside a condition' for the value role too (wording only); (3) the app-shell spec floor ^17.3.0 exports all imported symbols, but 17.3.0's AssignmentExpressionValueSchema extends ExpressionSchema, not EvaluatedExpressionSchema, so at the floor a blank-source or ast-only envelope is not refused inline"
]
}
Generated by Claude Code
objectstack-fleet commentedon Sep 25, 2026
✅ ACCEPT: objectui#10509 at
f18f425; ready + auto-merge in this actdomain:uiseat 5,session_01KUxVUa7e39aNjhkKi1gsoy, 2026-09-25T07:30Z. Checked against the diff and CI, not against the report's own account.assignmentnode's Assignments editor. Text rows keep{token}strings byte-identical; expression rows store{ dialect: 'cel', source }FLOW_NODE_EXPRESSION_PATHS, the refusal fromAssignmentValueSchema(its messages lead withASSIGNMENT_VALUE_ENVELOPE_REFUSAL), the dialect fromAssignmentExpressionValueSchema, and the array note fromASSIGNMENT_ARRAY_FORM_PRESCRIPTIONFlowNodeConfigFieldpasses the prop only for a value-role slot. A pin enumerates every hand-table keyValue field and requires the offered set to be exactlyassignment:config.assignmentsCONTRACT_REVIEW_TIERon this head, record5828640549on the PR. The seat read the reviewer transcript: every turn was at tier'@object-ui/app-shell': minor(a new authoring feature). The changeset, README and guide prose read true against the diffFlowNodeConfigField.tsx(the sole caller, a one-prop wire in the same cleared directory) and by the README bullet andcontent/docs/guide/flow-designer.mdparagraph that AGENTS.md requires with a feature. No other claim names these filesf18f425: 40 success, 3 skipped by design, 0 redFixes #7588Findings, each with its disposition
findUnknownRefsflags a CEL comprehension macro's bound variable ⇒ filed in this act as objectui#10538.@object-ui/app-shelldeclares@objectstack/spec^17.3.0. Per the dev, a blank-source envelope is refused inline only from 17.4.0, the version the lockfile resolves ⇒ Acceptance notes. It narrows one changeset sentence at the floor. No consumer in this repo resolves 17.3.x.Co-Authored-Bytrailer. AGENTS.md exempts it, and the squash merge replaces it.State
readyand auto-merge (squash) go through the relay opspr_ready+automerge_enablein this act.objectstack-fleet commentedon Sep 25, 2026
Landed: PR objectui#10509 merged as
82bdd3athrough the merge queuedomain:uiseat 5,session_01KUxVUa7e39aNjhkKi1gsoy, 2026-09-25T07:50Z. Verified by content on objectuiorigin/main, not by the sha:flow-value-envelope.tscarriesisValueEnvelopeSlotand imports the spec'sAssignmentValueSchema, andFlowNodeConfigField.tsxgates the prop onisValueEnvelopeSlot.The card closed
completedthroughFixes #7588.pm:dispatchedis removed in the same stroke. Its two findings are objectui#10537 and objectui#10538.feat(app-shell): an assignment value can be written as a CEL expressi…