Repository navigation
Description
triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH
The signature line above and the Blocked-by: line at the end were added by triage#1 when recording the ruling; the card text below is the filing seat's, unchanged.
Filing category: ② a decision only the maintainer can make.
维护者速读
- 现象:用户把 3 张发票发给 AI,AI 能读懂并通过 MCP 建好报销单,但发票文件本身只能走
create_upload_link:AI 给用户 3 个链接,用户在浏览器里把同样的 3 个文件再各选一次。一个链接只收一个文件,哪怕字段是multiple: true。 - 要你裁:① 一个链接能否一次传多个文件;② 能读本地文件的 AI 客户端(Claude Code、桌面代理)能否把文件内容直接交给 MCP(base64),不用再让人选一遍。
- 推荐:A(只做①:多文件字段的链接一次可选多个文件),② 暂不开。
One-sentence problem
"Here are my three invoices, file the claim" costs the user three link clicks and three re-picks of files they already gave the agent.
Evidence (reach)
Hosted tenant (pnpm dev:stack --with-ui, Team plan; cloud ff373e02, framework d7c3d44e), member's MCP OAuth token, claim object with invoices: { type: 'file', multiple: true }:
create_record→ draft claim;create_upload_link { field: 'invoices' }×2 → each link takes one file and appends it (record-file-access.ts:value = [...fileIdsIn(current[field]), fileId]);read_file(withfileId) reads each back;run_actionsubmits. Measured end to end.packages/mcp/src/mcp-http-tools.tscreate_upload_link: "uploads one file"; no MCP tool takes file content.
Governing text
- objectstack#30892 (closed): "An AI client sees an image the user attaches in its own chat, but it has no bytes to send. The MCP protocol has no standard client-to-server file transfer." → upload link, ruled option B (signed-in user only).
- That premise holds for chat clients; it does not hold for clients that read local files (they can send base64 in a tool argument).
Options
| Option | What it does | What the customer sees | Effort |
|---|---|---|---|
| A | On a multiple: true field, one link accepts several files (same user, same record and field, one use, same expiry) |
"Open this link and pick your 3 invoices" — once | storage seat · 1 PR · precedent: the console form already takes several files at once |
| B | A + an attach_file tool taking { objectName, recordId, field, filename, contentBase64 }, size-capped (e.g. 5 MiB), stored as the calling user and marked as added by an AI client |
Claude Code / desktop agents attach directly; chat clients still use the link | storage + mcp seats · 2 PRs · widens the write surface |
| C | Keep as is | One link per file | none |
Business reading: A removes the repetition for everyone without a new door; B removes the re-pick for agents with file access but lets an agent put bytes the user never picked onto a record (an agent could generate a plausible "invoice" PDF), so provenance becomes the reviewer's problem.
Recommendation
A. It cuts the cost of the common case (several invoices) with no new trust question. Confidence gap: whether B's convenience is worth the provenance question for expense-type records; recommend revisiting B when a customer workflow needs agent-side bulk attach.
Self-check: ① and ③ pick A; ② (business pull) would pick B for Claude Code users; ④ picks A.
After the ruling
A: the upload-link page takes multiple on a multi-file field, appends each file in one write, e2e: one link, three PDFs, three files on the field.
os-decision-facets
① 项目长远合理性: A 沿用已裁定的链接通道,只放宽"一个链接一个文件"。
② 实际业务拉动: 报销一次常有多张发票(本次实测 2 张需 2 个链接);能读本地文件的 agent 用户更想要 B。
③ 防 AI 犯错: B 让 agent 能写入用户没亲手选的文件(可伪造发票);A 仍由人选文件。
④ 创业阶段不扩散: A 一个 PR;B 新增写入工具和来源标记。
Prior rulings read: #30892 (upload link, option B sign-in only); thread: none
Reader who acts: the storage seat (upload-link door) and the packages/mcp seat, after the ruling.
Dedupe: repo:objectstack-ai/objectstack is:issue mcp upload base64 file content → 9 hits, QA runs only; upload link multiple files one link → 22 hits, none on this; mcp attach file bytes agent local file → 258 hits, none on this.
Blocked-by: #32686
Activity
os-bob commentedon Nov 12, 2026
Clarification from the user who raised this: the real scenario is a photo of the invoice sent to the AI in a chat app. There the client has no bytes to forward, so option B does not help this user at all; only A (one link, several photos picked from the phone album at once) reduces their steps. This strengthens the recommendation for A and leaves B for agents with local file access only.
Not yet measured: a phone photo (JPEG) through the link (the PDF path is measured). The field's
acceptlist has no.heic; iOS usually converts to JPEG when a browser file input picks from Photos, but that is unverified here.Generated by Claude Code
os-bob commentedon Nov 12, 2026
Measured for option B (2026-11-12, same tenant): an agent running on the user's machine with the invoice photo as a local file (JPEG). It reads the photo (amount ¥268.00, meal, invoice number), files the claim over MCP (
create_record→ draft with amount 268), and then has no way to attach the photo: no MCP tool takes file content, and the same MCP OAuth token onPOST /api/v1/storage/upload/presignedanswers401 AUTH_REQUIRED(correct — the token is bound to the MCP resource). Its only route is an upload link, so the user re-picks a file the agent already holds.So the business pull for B is real for local agents (Claude Code, desktop agents), not hypothetical. Updated recommendation: A now, B next, B with the provenance mark (added by an AI client, which client), the size cap, and the same edit-door check as the link. The forged-file concern is unchanged and is the reason B carries the mark rather than being refused.
Generated by Claude Code
objectstack-fleet commentedon Nov 12, 2026
triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH
Path: none
分诊首触 + 决策分析(fable 档独立评估)· 2026-11-12T16:55Z
维护者速读
是否已有裁决:
前提核实:
sys_file行对应一个链接、一个文件。A 要改已评审过的「单次使用」条件,工作量比卡上写的 1 个 PR 高。multiple: true的文件字段为 0。attach_file(base64)四轴:
推荐 A,与 #32686 合成一次设计。置信缺口:手机相册 HEIC 格式未实测;上限数值待定。自检: 只看①选 A;不翻转。人工底线项:B(新写入门)。
os-decision-facets
Prior rulings read: #30892 ruling B and its Ask 3 (deferred, not refused); ADR-0104 D3; thread: 2 comments, no ruling
Generated by Claude Code
objectstack-fleet commentedon Nov 13, 2026
eval#1 · os-bob · session_012dn2AcTB69pEwZ9ugr1g1w
More reach for ② (agent holds the bytes) — and a case on a child-table file field · 2026-11-13T03:05Z
Measured on the
os-dev-testenvironment today, with a third-party local agent (WorkBuddy; models DeepSeek-V4.1-Flash / GLM-5.3-Flash) operating an OA app over MCP for its user.The case. The user handed the agent two invoice PDFs for a travel expense claim. The app models the claim as a header (
c982_expense_claim) with line items (c982_expense_item,master_detail→ claim); the invoice is afilefield on each line item, one invoice per line, so the approver checks each amount against its own invoice.create_upload_linkonce per line item → two links, each bound to one child record + field, ~10 minutes. It then told the user: 「我这边的 OA 连接没有『直接推送文件』的能力……链接必须在你自己登录 OA 的浏览器里打开,选择本地 PDF 上传」. The user re-picks two files the agent already has, one link per line.Two things this adds to the card.
multiple: truefield". Here each invoice belongs to a different child record, so it is still one link per line no matter how many files a link accepts. N line items = N links = N re-picks. Only B (an agent that holds the bytes hands them over) removes the round trip for line-item attachments, which is the normal shape of an expense claim.record.claim.approval_status, so the approver reviews exactly what was submitted). The agent had to stop and ask the user to upload before submitting — "一旦提交进入审批中,明细会被锁定,发票就传不进去了". With links that expire in minutes and a human in the loop, a missed upload means recall → upload → resubmit (and recall is itself not available to agents yet, MCP: a submitter's agent cannot recall or resubmit its own approval request — approval_recall / approval_resubmit are not ai.exposed, while the approver's four decide actions now are (ruling A on #32442) #32993).Neither changes the security concern triage raised for B (an agent can attach a file the user never picked). It does mean the business pull for B covers local agents on master–detail forms, where A gives no relief.
Generated by Claude Code
objectstack-fleet commentedon Nov 13, 2026
triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH
Brief-claimed-by: session_01Q4EjjMnyLqJdzuktEmRPXH · batch #1 · 2026-11-13T04:20Z
Decision batch #1 (the MCP agent-capability family, under epic #32897), taken on the maintainer's 「5张一批按总监决裁格式给我」.
objectstack-fleet commentedon Nov 13, 2026
triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH
Ruling recorded · #32747 · A · 2026-11-13T04:41Z
Maintainer, verbatim (triage#1 session): 「以上原则同意」, answering decision batch #1 (
32641 B / 32591 A / 32592 C / 32686 A / 32747 A). The batch was analysed independently at the fable tier, with prior rulings checked first; the full analysis was presented in the session.needs-user-decision→pm:blocked,Blocked-by: #32686(one design, [Decision] How should an MCP agent create a record whose required file field must be filled (e.g. an expense line with a required e-invoice)? #32686 first).Generated by Claude Code
1 remaining item
objectstack-fleet commentedon Nov 13, 2026
triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH
Path: none
Triage: p0 → p1 · not an 18.4.0 blocker · 2026-11-13T07:57Z
⛔ Not a claim; the claim and state are unchanged, and the work continues.
mainor merge-queue red and the release window's blockers (「阻塞卡升 p0」). These six cards are MCP approval and upload capability work, not 18.4.0 regressions, so they carry p1: the highest working grade, first in their lanes after the blockers. That keeps the intent of the 07:27Z regrade (「…这些也都开始处理.这样我们审批相关的MCP就非常好用了.」) without holding the cut.Generated by Claude Code
objectstack-fleet commentedon Nov 13, 2026
triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH
Path: none
Triage: unblocked →
pm:queue· 2026-11-13T18:46ZBlocked-by: #32686: closed. PR feat(storage,mcp): an upload link for a file field of a record not yet created #33123 (upload link for a record not yet created) merged as671888494c. The maintainer's letter on this card (batch 1, A) stands, so the implementation can be dispatched. Grade unchanged (p1, MCP focus).objectstack-fleet commentedon Nov 13, 2026
domain:server#7 · os-tesla · session_01L9qibzuZJBQY4wUQFJbkfx
Claim: PM loop round 8, card thread · 2026-11-13T19:53Z · p1, ruling A 6844223218
Session:
session_01L9qibzVFJSNs6XZjC8jkNJAccount:
os-teslaBranch:
claude/project-thread-4i7bf9Worktree:
objectstack-issue-32747Domain:
domain:serverSeat:
domain:server#7File surface:
packages/services/service-storage/src/(upload-link store,storage-routes.tslinkPUTdoor,pre-record-upload.ts,record-file-access.ts,upload-link-copy.tsand the page),packages/mcp/src/mcp-http-tools.ts(thecreate_upload_linkdescription and answer),packages/mcp/src/skill-md.ts+ui/apps/console/src/pages/developer/objectstack-skill.ts,content/docs/ai/connect-mcp.mdx,content/docs/permissions/system-context.mdxrows that describe link use, their tests, a dogfood test, one root changeset. ⛔ No base64 input, no new MCP tool, nopackages/specchange without stopping first. Stop on breach.Container & model:
L,mode:thread,model: default (dispatch-gates --tier: no path-derived mandate; PM default),effort: highClause-②: yes (widening) — a link on a
multiple: truefield accepts several files, each answered with its own id; a single-file field link and an attachments link stay one fileMetadata-protocol: none
Responsibility: the one-file limit sits in the upload-link door in
service-storageand is stated bycreate_upload_linkinpackages/mcp(objectstack) | the console form already takes several files at once; #32686 already answersfileIdsas an array | every MCP user attaching several files to a multi-file field reaches it todayThread-read: 6861932019
Premise: holds (measured end to end in the card's evidence; #32686 merged since, so the dev re-measures at
origin/mainfirst)Serial constraints cleared:
mcp-http-tools.tsis shared with this seat's #32872 (PR #33134), #32530 and #31699;storage-routes.tslast changed by merged PR #33142; edits stay on the upload-link paths; land serially, merge main after a sibling landsobjectstack-fleet commentedon Nov 13, 2026
domain:server#7 · os-tesla · session_01L9qibzuZJBQY4wUQFJbkfx
Claim amendment to this seat's claim 6863322469 on #32747 · 2026-11-13T19:57Z: the file surface adds
packages/runtime/src/domains/mcp.ts: the MCP file bridge passes the new optionalcountthrough to the pre-record link mint. The edit stays on that one member; the file is shared with this seat's mcp: validate_expression still answers ok where the validation/formula gate refuses (null-guard, undeclared hop field, and the rest of the gate's private passes) #32872 (PR fix(mcp): validate_expression gives the publish gate's verdict at validation and formula sites #33134) and mcp: describe_object serves formula, autonumber and edit-denied fields as readonly: false #32530, so landing is serial.packages/mcp/src/file-content.ts: theMcpFileBridgetype gains the optionalcount.The shape is in
out/32747-proposed-shape.md. The seat accepts it with a pre-record link taking exactlycountfiles, and attachments links staying one file.objectstack-fleet commentedon Nov 13, 2026
domain:server#7 · os-tesla · session_01L9qibzVFJSNs6XZjC8jkNJ
os-dev-report
{ "issue": 32747, "status": "done", "branch": "claude/project-thread-4i7bf9", "pr": "https://github.com/objectstack-ai/objectstack/pull/33151", "session": "session_01L9qibzVFJSNs6XZjC8jkNJ", "premise_still_valid": true, "summary": "A link for a `multiple: true` file field takes up to UPLOAD_LINK_MAX_FILES (10) files in one multipart submit (count + file0..fileN-1); each file gets its own id and the field gains them all in one write, or none (any store or the write failing discards every file of the send). A link for a record not yet created takes `count` (1-10, >1 only on a multi-file field) and answers that many fileIds, the extras reserved as pending rows refused to every write before the upload. Single-file field links and attachments links stay one file with unchanged refusals; success answers gain the additive `fileIds`. create_upload_link gains optional `count` (no new tool); the page takes `multiple`; skill-md, connect-mcp.mdx and system-context row 47f updated; the Console skill mirror needs no change (no-scope skill, no file tools). Head 6a6df23affbd19a8d4842f8746eed3e78e005ebd.", "tests": "service-storage vitest: 83 files / 1566 tests pass (record-file-access.integration.test.ts 63, 13 new under #32747). mcp vitest: 63 files / 1109 pass (5 new). dogfood showcase-mcp-record-files.dogfood.test.ts 21/21 (3 new: one link three PDFs, count 3 + create_record, size refusal of one of three). Ablations via scripts/ablation-replace.mjs, each restored with git diff HEAD empty: count check dropped (3 red), pre-spend accept/maxSize only for pre-record (1), pre-record discard last only (1), record discard last only (2), reserved rows committed (4), same-user check dropped (2), single-file link takes 10 (6), mint count>1 on single-file (1), mint cap dropped (1), form part checks off (1), MCP count+recordId passed through (1), size limit first file only in rebuilt dist (dogfood 1 red: 200, three stored). Typecheck service-storage, mcp, runtime, dogfood: exit 0.", "gates": "dispatch-gates --commands at 6f09953b: 106 commands + 4 artifact-roster gates under changed dirs (check:changeset-fixed, check:error-code-casing, check:filter-alias-parity, check:route-ledger-census), each with </dev/null, exits on disk: 110 exit 0. check:docs (exit 1, stale json-schema after the merge), check:skill-examples (exit 3) and check:dual-build-cjs-loads (exit 3) rerun after pnpm --filter @objectstack/spec build: exit 0. --ran: 106 derived, 106 run, 0 NOT-MEASURED, 0 UNRUN. pnpm lint: exit 0. CI check-runs on the PR: not yet reported.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm; the touched dogfood file was run (21/21)", "mcp_calls": "5 — issue_read (get x1, get_comments x4); 0 write tools", "api_writes": "0 REST proxy writes. Relay writes: 2 (pr_create #33151 with assignees os-tesla; this os-dev-report comment). git pushes to claude/project-thread-4i7bf9 only.", "open_questions": [], "security_findings": "withheld", "out_of_scope_findings": [] }[-][Decision] Should an AI client be able to attach a file it already holds without the user re-picking it through an upload link (and may one link take several files)?[/-][+]storage + mcp: one upload link on a multiple: true file field accepts several files (same user, record, field, one use, same expiry) — ruled A[/+]