Skip to content

CONTRACT: feat(ui-server): admit generic chat files as durable server-owned originals - #3342

Merged
schlessera merged 12 commits into
epic/3150-attachmentsfrom
feat/3153-generic-chat-file-admission
Nov 4, 2026
Merged

schlessera merged 12 commits into
epic/3150-attachmentsfrom
feat/3153-generic-chat-file-admission

Conversation

@schlessera

@schlessera schlessera commented Nov 2, 2026 •

Copy link
Copy Markdown
Owner

Refs #3153
Closes #3153 when the epic batch (epic/3150-attachments) lands on main. This PR targets the epic branch, so GitHub will not close the issue on merge.

What changed

The host now admits any composer file as a real, read-only original the agent can open. The new neutral route POST /api/drafts/:draftId/files streams one file into .brain-ui/attachments/<fileId>/<name>. It creates no inbox, queue or triage work. It routes on the stored bytes (signature, then strict UTF-8, then the shared track parser), so a PDF, text, ordinary JSON or unknown binary is stored without any track rejection, while a valid GPX/KML/GeoJSON keeps the existing parser's evidence. Uploads reserve room in a configurable host pool (BRAIN_UI_ATTACHMENT_POOL_BYTES, default 1 GiB). Drafts bind originals with fileIds, and a held or unconfirmed send keeps its files in one outgoing snapshot; both live in the operational database and survive reload and restart. chat_message.attachmentRefs sends them. The host resolves every id against its own records (lineage, binding, state) and against the bytes on disk (size, SHA-256, no link) before any backend runs. It appends a quoted <brain-attachments> block with read-only paths and never injects the contents. Acceptance writes the conversation's reference before it consumes the draft revision. GET /api/attachments/:fileId reads an original back after a digest check. Cleanup removes never-bound uploads after one hour and formerly referenced ones seven days after their last reference went, with a two-phase mark-then-unlink sweep and boot reconciliation. Governed workers see the store read-only, and reads taint the session as chat_attachment.

This is #3153's slice of the approved design (docs/plans/chat-attachments.md §4 and §6, plus the §7.1 block). Content reading (#3154) and the composer (#3155) are not in it.

Why this shape

This follows the approved design and the maintainer rulings recorded in docs/decisions/chat-attachments.md. It departs from the design in three places, each recorded under "What #3153 built" in that decision record:

  • chat_message.attachmentRefs instead of a new files variant. Adding { kind: "attachment" } to files would retype ChatFileAttachment[] for TypeScript readers; two ui-react tests reading .path failed to compile when I tried it. The contract counts a retype as breaking, so I used a separate optional field. It is additive and carries the same reference.
  • Owner ids. outgoing is keyed by draft id (a draft has at most one snapshot) and message by session id. A draft id may contain :, which made the design's compound ids ambiguous.
  • No read_attachment in the block text. That tool belongs to backends: read PDF and generic attachment contents with bounded fallbacks #3154 and is not registered yet, so the block says the agent may open the path with its file tools.

Proof

Base origin/epic/3150-attachments @ 4cd19855219c8bcdfafb8a1104161a90093c5796. Head b2c0cbcdd83eaab42e6d14973d50d071f95a2a92.

bun run check:pr --base origin/epic/3150-attachments   # exit 0: lint/leakage, env docs, changesets, strict typecheck, affected fast invariants (1003 pass, 0 fail)
NODE_OPTIONS=--max-old-space-size=8192 bun run typecheck   # clean
bun run test packages/ui-server/tests/attachment-*.test.ts packages/ui-server/tests/share-routes.test.ts \
  packages/ui-server/tests/track-intake.test.ts packages/ui-server/tests/ws-track-files.test.ts packages/ui-server/tests/drafts-*.test.ts
# 101 pass, 0 fail

New suites: attachment-detect, attachment-routes (real composition root), attachment-ownership (real composition root and real WebSocket dispatch) and attachment-sweep. Every claim about an original reads the actual file at the path the agent was given and compares its SHA-256, for image, PDF, text and track fixtures.

Failing-first witness. attachment-routes "a PDF the track route refuses…": /api/track-upload answers 422 unsupported_track for the Odysseus PDF, then the composer route stores it (201, kind: pdf, byte-identical on disk, mode 0444, nothing under .brain-ui/inbox).

Mutations. Each was applied, the named test failed on the stated assertion, then restored:

Mutation Test Failed on
Save skips the lineage check ownership "references the client cannot back…" cross-draft save: expected [400, ATTACHMENT_INVALID], got [200, undefined]
Reservation charges 0 and skips the pool check sweep "concurrent uploads at the pool edge…" third upload while two stream: expected 507 ATTACHMENT_CAPACITY, got 201
Bind without requiring state = stored sweep "a rebind that loses the race…" expect(refused).toBeInstanceOf(DraftError)
Serve without the digest check routes "the pool refuses honestly…" altered original: expected [409, false], got [200, true] (altered bytes served)
Dispatch ignores draft binding ownership "references the client cannot back…" expect(h.starts).toHaveLength(0) (a backend ran)
Dispatch skips the on-disk read same expect(h.starts).toHaveLength(0)
Rebind keeps the old clock and the mark skips the reference re-check sweep "seven days…" a rebound file was swept: expected [], got [fileId]
Governed resolver drops the attachment area governed-worker "chat attachment originals are mounted read-only…" expected the read_only entry, got undefined
Taint area removed ingestion-taint G6_SOURCE_Read

The cleanup protection uses two independent guards: rebinding clears the clock, and the mark requires no reference. Either one alone keeps a rebound file, so a single-guard mutation survives (checked for the reference re-check alone). Only the combined mutation fails.

Collisions (bun scripts/collisions.ts --json --paths …): path collisions with #3305 (ws/dispatch.ts), #3316 (ui-sdk/src/protocol.ts) and #3335 (ws/run-session.ts), plus closure-only reports. Main also already has 074_untrusted_marks.sql, so this migration is 075_chat_attachments.sql; ingestion-taint.ts changed on main as well and will need a merge when the epic lands.

Environment limits. This sandbox has no unprivileged user namespaces. ui-backend-claude/ui-backend-pi provider-history, governed-worker's "/run mask refuses a host Unix socket" and the worker-boundary suites fail identically on the unchanged base checkout. The provider-history guard therefore needs the hosted proof. The turn path only appends a new block to new user messages; earlier messages, tools and system prompt are untouched.

Ready-candidate run 41505209464 (head b2c0cbcdd): diagnosis and fix

New head 1a060a04194d420f293e1007e3a4f6dfacd70383, base unchanged at 4cd19855219c8bcdfafb8a1104161a90093c5796.

  • unit (4): a real gap, fixed in 1a060a041. real app method/path inventory equals the complete classified HTTP specification received two mounted routes the spec does not list: POST /api/drafts/:draftId/files and GET /api/attachments/:fileId. Both are now S rows in docs/http-api.md, and the declared-endpoint prose counts are 161. Each has a coverage-matrix row in docs/http-api-coverage.md, a mounted-handler check in http-supported-mounts.test.ts and an explicit lifetime in http-mount-lifetimes.ts. The upload check is a real 201 that stores a text original, verified in attachment_files and as byte-identical on disk. The read-back check is a shared reader that answers a typed 404 ATTACHMENT_NOT_FOUND with no-store. The two added check lines moved the checkHandler citation in http-api-coverage.md, now 173-192.
  • unit (1): runner disk pressure (tests: unit shard intermittently fails subscription-status, turn-scope and answer-delivery liveness together #3256), not this change. All 8 failures in worker 1 come from the turn free-space floor max(5%, 2 GiB):
    • The continuity runtime fixture refused with TurnFixtureFreeSpaceError: 2,665,242,624 bytes available, 3,844,357,734.4 required. That is the (unnamed) failure.
    • The session-taint turn carries a free_space_floor stop receipt.
    • The six subscription-status tests got provenBy/lastProvenAt null, because the fixture turn was stopped before it succeeded.
    • Worker 2's html-preview-runtime failures followed its SIGKILL (exit 137) after worker 1 failed.
    • Controlled reproduction: a throwaway preload, not committed, reports CI's free-space figures for the test brains. With it, subscription-status.test.ts fails the same tests at the same lines on both the PR head (:307, :321, :345, :363, :501, :539) and the unchanged epic base 4cd198552. Both pass 32/32 without it. Evidence is on #3256. No change here; the fixture attribution belongs to tests: unit shard intermittently fails subscription-status, turn-scope and answer-delivery liveness together #3256 and tests: attribute low-space failures in remaining running-turn fixtures #2551.

Local proof at 1a060a041:

NODE_OPTIONS=--max-old-space-size=8192 bun run typecheck                 # clean
bun run check:pr --base origin/epic/3150-attachments                     # exit 0, affected fast invariants 1230 pass, 0 fail
bun test tests/http-inventory.test.ts tests/http-supported-mounts.test.ts tests/subscription-status.test.ts   # new and inventory checks pass

http-supported-mounts.test.ts also has 9 failures that come from the host (/api/brain/* and POST /api/schedules/proposals: unprivileged user namespaces are unavailable). They are unrelated to this diff and passed in the hosted unit (4).

Collisions at 1a060a041 (bun scripts/collisions.ts --json --pr 3342): textual with #3056, #3059, #3316, #3343, #3360 and #3371; closure-only with 23 PRs (#3054, #3069, #3116, #3292, #3293, #3295, #3309, #3317, #3320, #3325, #3335, #3336, #3341, #3345, #3352, #3362, #3365, #3366, #3367, #3368, #3369, #3370, #3372).

Refresh on 2026-11-03 (head 5e8939bce)

Merged origin/main at 559fe7b41, which carries the merged blocker #3385. epic/3150-attachments at 4cd198552 was already contained. Conflicts and resolutions:

  • Code, both intents kept. drafts/store.ts: the list query reads file_ids, outgoing_json and keeps main's retired-session filter, and save runs main's refuseRetired before this PR's file-aware fingerprint. IngestionSource and the governance taint enum have both chat_attachment and main's user_marked/apify_result. ws/connection.ts imports both CHAT_DELETION_CAPABILITY and COMPOSER_FILES_CAPABILITY. schemas.ts takes main's hygiene imports plus attachmentFileSchema.
  • Contract docs, both sides kept. integration-contract.md, package-api.md and wire.md keep this PR's sections beside main's. http-api.md lists the composer-file routes, counts 170 routes (main's 168 plus this PR's 2), and adds chat_attachment to main's taint sources. supported-inputs.md adds BRAIN_UI_ATTACHMENT_POOL_BYTES to main's list.
  • Citation-number-only hunks in 14 decision, plan and investigation docs take main's text.
  • API reports are regenerated.

Fixes after the merge:

  • 66421db5f re-adds chat_attachment to IngestionSource, which the merge had dropped; typecheck and build had failed on it.
  • 2b08bd8fb renumbers the migration to 077.
  • 5e8939bce fixes a test race. http-supported-mounts "a dirty failed mount discards its resources" failed 3 of 3 runs on the merge. It passed on origin/main and on the pre-merge head 1a060a041. Main's CONTRACT: feat(ui-server): attribute admission to timed steps and prepare the turn journal while idle #3386 starts a journal idle pass at boot, and its native calls held a seventh socket while the case counted six. The test now awaits journal.maintain() first, and 3 of 3 runs pass.
bun install --frozen-lockfile; bun run typecheck; bun run build; bun run lint   # all exit 0 after the fixes
bun run test <the 7 PR tests> drafts-{http,send,settings-offloop,store} governance-events{,-http} http-inventory ws-chat-deletion chat-deletion db-migration-runner
  # 217 pass, 10 fail. All 10 need unprivileged user namespaces: governed-worker "/run mask refuses a host Unix socket", and the same 9 http-supported-mounts brain/schedule handlers that fail identically on clean origin/main here ("unshare: write failed /proc/self/uid_map")
bun run check:pr --base origin/epic/3150-attachments   # exit 0; affected fast invariants 1346 pass, 0 fail

Refresh after the epic landed on main (2026-11-04)

epic/3150-attachments landed on main (#3523), so the epic tip is main @ 0760f7832f3d81f897f7925b417e2a53dd2465b8, which also carries epic #1342's turn-path split and epic #3103 (external compute). Merge f36aa2948 brings it in; head d73637092.

  • Ported, not pasted back: the chat_message admission and envelope changes from ws/dispatch.ts now live in ws/handlers/chat-message.ts; the Retry changes in ws/handlers/retry.ts. From ws/run-session.ts: run input in session-run/types.ts, the first request and turn.attachmentFileIds in session-run/slot.ts, the retry request's attachmentRefs in session-run/turn.ts, message source and prompt (withChatFiles) in session-run/backend-entry.ts; queueing, native follow-up and acceptDraft fileIds stay in run-session.ts.
  • chat_attachment sits beside main's compute_output taint source (type, governance event enum, HTTP doc). The contract index keeps both sections. api-reports regenerated with bun scripts/api-report.ts.
  • Main's unused-export gate (chore(ci): refuse exports no other file imports in ui-server, ui-kit and ui-react #3448) refused seven attachment exports: hasRef (no caller) is deleted, the rest are module-local now, and so are the draft-store input interfaces.
NODE_OPTIONS=--max-old-space-size=8192 npx tsc --noEmit -p .   # exit 0
bun test --timeout 30000 <attachment-*, ingestion-taint, http-supported-mounts, drafts-store, ws-retry-turn, ws-track-files, ws-message-source, ws-handlers, track-intake, chat-deletion, *-offloop>
  # 245 pass, 9 fail: the 9 http-supported-mounts brain/schedule handlers that need unprivileged user namespaces. Unsandboxed, origin/main fails the same 9 on this host (103 pass on main, 105 here with the two new mounts)
bun test (attachment-*, ws-retry-turn, ws-track-files, ws-message-source, drafts-store) after the export change   # 53 pass, 0 fail
bun run check:pr --base origin/epic/3150-attachments   # exit 0, receipt below

Refresh onto the #3550 fix (2026-11-04)

The failed-job rerun of run 41773863563 is not merge evidence. epic/3150-attachments was fast-forwarded to main b6ddb64241161a404a0aca2c13a52f7dbe1a2254, which contains the #3550 fix (#3552), #3557 and #3560. Merge 88999ac36 brings it in.

  • http-supported-mounts.test.ts takes main's ui-server: dirty failed mount case intermittently counts a seventh native socket #3550 join (journal.ready, maintain(), releasesSettled()). This PR's own maintain() await (5e8939bce) is dropped: main's join already runs maintain() and also waits for the release that held the seventh socket, so the await tested nothing of its own.
  • fix(ui-sdk): observe again after an empty worker mount table during sandbox setup (#2701) #3560 overlapped in worker-protection.ts, which merged cleanly. Its citations in governed-writable-workers.md now point at the merged lines: requireWorkerBrainMount at 379-388 and verifyWorkerFolderMounts at 391-432, both bodies unchanged.
  • docs/http-api.md keeps main's inventory, adds the composer-file routes and counts 171 endpoints: 168 at the merge base, plus this PR's 2, plus main's chat-deletion read. The contract index keeps both sections, and the api-reports are regenerated.
bun install --frozen-lockfile; bun run build   # exit 0
NODE_OPTIONS=--max-old-space-size=8192 npx tsc --noEmit -p .   # exit 0
bun test --timeout 30000 (unsandboxed) attachment-*, http-supported-mounts, http-inventory, governance-events, ingestion-taint, drafts-store, ws-retry-turn, ws-track-files, ws-message-source, ws-handlers, track-intake
  # 233 pass, 9 fail: only the 9 brain/schedule handlers that need unprivileged user namespaces on this host (same on main). The dirty-mount case passes.
bun test --timeout 30000 packages/ui-sdk/tests/governed-worker.test.ts   # 20 pass, 1 fail: "/run mask refuses a host Unix socket", which also needs user namespaces (noted above)
bun test -t 'dirty failed mount' x10   # 10 pass, 0 fail
bun run check:pr --base origin/epic/3150-attachments   # exit 0, receipt below
{"version":1,"kind":"brain-check-pr-receipt","head":"88999ac36d3eb1ab19b533eb382ac41673c13c31","base":"origin/epic/3150-attachments","mode":"focused","startedAt":"2026-11-04T12:37:16.359Z","finishedAt":"2026-11-04T12:38:29.163Z","checks":[{"name":"all lint and leakage gates","categories":["changeset"],"outcome":"passed","elapsedMs":10740.303833},{"name":"environment documentation","categories":["changeset"],"outcome":"passed","elapsedMs":93.04272499999934},{"name":"pending changeset package names","categories":["changeset"],"outcome":"passed","elapsedMs":82.87397699999929},{"name":"contribution changeset","categories":["changeset"],"outcome":"passed","elapsedMs":18.789601000000403},{"name":"affected fast invariants","categories":["verify","unit"],"outcome":"passed","elapsedMs":61851.899781}]}

Checklist

  • Focused local preflight/behavioral receipts and complete selected hosted proof passed; actual head/base/job checkout evidence is in Proof. (Local receipts above; hosted ready-candidate confirmation requested.)
  • Acceptance criteria from the issue are all met, or the gap is named below.
  • The PR and completed/advanced issues have verified release milestones under docs/process/github.md.
  • A changeset is included, or this changes nothing a consumer can see.
  • No personal data anywhere in the diff — the leakage gate covers the whole tree.
  • The integration contract index or relevant component is updated in this same commit, or nothing in the machine surface moved.
  • Docs under docs/ reflect the change, or nothing documented moved.
  • Provider history stays append-only (docs/decisions/provider-history.md): the provider-history guard passes, or nothing on a turn's path changed. (Hosted proof pending; see environment limits.)

Anything left open

🤖 Generated with Claude Code

schlessera and others added 4 commits November 2, 2026 18:43
…rver-owned originals (#3153)

Add the neutral composer-upload route POST /api/drafts/:draftId/files.
It streams one file into .brain-ui/attachments/<fileId>/, routes on the
stored bytes (signature, strict UTF-8, then the shared track parser) and
reserves room in a configurable host attachment pool
(BRAIN_UI_ATTACHMENT_POOL_BYTES, default 1 GiB). It creates no inbox,
queue or triage work.

Drafts bind originals by fileIds, and a held send's files by one outgoing
snapshot, in the operational database. Both survive a reload and a
restart. chat_message.attachmentRefs sends them: the host resolves each
against its own records and verifies the bytes on disk before any backend
runs. It gives the agent read-only paths in a quoted <brain-attachments>
block and never injects the contents. Acceptance writes the conversation's
reference before it consumes the draft revision. GET
/api/attachments/:fileId reads an original back after a digest check.

Cleanup removes never-bound uploads after an hour. Formerly referenced
originals go seven days after their last reference. Deletion is two-phase
(mark, then unlink) and reconciled at boot. Governed workers mount the
store read-only, and reads taint the session as chat_attachment.

Additive contract change; documentation, decision records and a minor
changeset are in this commit.

Refs #3153

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
)

Refs #3153

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Regenerate the ui-sdk and ui-server API reports for the additive composer
attachment exports, and document the server's new config field and message
source argument in the package API component.

Refs #3153

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#3153)

The pool reservation is the declared Content-Length, so a longer body is
refused before its extra bytes reach the disk uncharged. Add proof for
the chat_attachment taint area and the governed read-only mount of the
attachment store.

Refs #3153

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@schlessera schlessera added this to the 0.45.0 milestone Nov 2, 2026
@schlessera schlessera added type: feat A new capability or a change in user-visible behaviour. area: ui-sdk Wire protocol, runtime schemas, the server and client SDK surfaces. area: ui-server Hono app factory: turn coordinator, auth, session catalog, routes, cron. contract Touches the integration contract: CONTRACT: commit, contract doc updated in the same commit. labels Nov 2, 2026
@schlessera

Copy link
Copy Markdown
Owner Author

Collision report at head b2c0cbcdd83eaab42e6d14973d50d071f95a2a92 (bun scripts/collisions.ts --json --pr 3342): 6 textual collisions, 18 closure-only reports. The textual ones are mostly moved citations:

Detection only, not a lock. Whichever lands second re-runs bun scripts/check-citations.ts and bun run api-report.

…ventory (#3153)

POST /api/drafts/:draftId/files and GET /api/attachments/:fileId were
mounted by createApp but missing from the classified HTTP specification,
so the real-app inventory test refused the tree. Both are supported (S)
rows now, the declared-endpoint counts are 161, and the coverage matrix
names each one's mounted-handler receipt: a real upload that stores the
original read-only, and a typed 404 for an unknown file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
schlessera and others added 2 commits November 3, 2026 16:12
…merging main

The merge resolution took main's IngestionSource union, which adds
user_marked and MarkedLookup, but did not re-add this PR's chat_attachment.
Typecheck and the build refused the attachment classifier. Add it back, and
regenerate the api reports, whose merged rows were duplicated or out of order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e sockets

Since #3386, createApp starts a turn-journal idle maintenance pass after
journal.ready, and that pass makes native manager calls in the background. In
the merge of this PR with origin/main the pass was still in flight when the
dirty-mount case opened six ready() sockets, so it counted seven: 3 of 3 runs
failed, while origin/main and this PR's pre-merge head each passed. Await
journal.maintain() first. That settles the startup pass, whose one keep-alive
socket the six calls reuse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@uploads-sh

uploads-sh Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

No attachments are currently associated with this pull request.

Maintained by uploads.sh · uploads put <file> --pr 3342 · docs

schlessera and others added 2 commits November 4, 2026 04:10
…split

Epic #1342 split ws/dispatch.ts into ws/handlers/* and ws/run-session.ts
into ws/session-run/*. The attachment admission and retry changes move to
handlers/chat-message.ts and handlers/retry.ts; the run input, first-request,
turn binding, retry request, message source and prompt changes move to
session-run/{types,slot,turn,backend-entry}.ts. The chat_attachment taint
source sits beside main's compute_output, the contract index keeps both
sections, and the api-reports are regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main's unused-export gate (#3448) arrived with the epic merge. hasRef had
no caller; the others are used only inside their own module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@schlessera
schlessera marked this pull request as draft November 4, 2026 04:32
@schlessera
schlessera marked this pull request as ready for review November 4, 2026 04:32
@schlessera

Copy link
Copy Markdown
Owner Author

Collisions at head d73637092bdd7a76ac43e07f2fd824405183f9ca (bun scripts/collisions.ts --json --pr 3342), recorded before requesting ready confirmation: textual overlap with #3393, #3449, #3456, #3458, #3464, #3491 and #3524 (contract index, api-reports, governed-workers decision); closure-only reports with #3054, #3056, #3059, #3069, #3116, #3336, #3413 and #3437. None blocks this sub-PR merging into its epic. #3456 and #3524 will be refreshed after this lands.

@schlessera

Copy link
Copy Markdown
Owner Author

Ready proof run 41773863563 at head d73637092 (base 0760f7832) failed only unit (3): "a dirty failed mount discards its resources before the next independent case" counted 7 native sockets, expected 6. The same isolated case fails on clean main 0760f7832 at a similar rate locally, so it is a pre-existing flake, filed as #3550. Rerunning the failed jobs of that run; no code change.

@schlessera

Copy link
Copy Markdown
Owner Author

Correction: the failed-job rerun of run 41773863563 is not merge evidence for this PR, whatever its result. #3550 reproduces on plain main, so it is a real failure, not one to rerun into green. This PR merges into its epic only on a fresh ready proof after the #3550 fix is on main and in epic/3150-attachments, and after this branch is refreshed onto it.

 fix

- http-supported-mounts.test.ts takes main's #3550 join (ready, maintain,
  releasesSettled). It subsumes this PR's own maintain() await, which is
  dropped as redundant.
- The contract index keeps both sections; http-api.md's inventory names the
  composer-file routes and counts 171 endpoints (168 at the merge base, plus
  this PR's 2, plus main's chat-deletion read).
- governed-writable-workers.md cites requireWorkerBrainMount and
  verifyWorkerFolderMounts at their merged lines.
- api-reports regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@schlessera

Copy link
Copy Markdown
Owner Author

Collisions at head 88999ac36d3eb1ab19b533eb382ac41673c13c31 (bun scripts/collisions.ts --json --pr 3342), recorded before requesting a fresh ready confirmation against epic tip b6ddb6424: textual overlap with #3393, #3449, #3458, #3464 and #3491; closure-only reports with #3054, #3056, #3059, #3069, #3116, #3336 and #3437. None blocks this sub-PR merging into its epic.

@schlessera
schlessera marked this pull request as draft November 4, 2026 12:39
@schlessera
schlessera marked this pull request as ready for review November 4, 2026 12:39
@schlessera
schlessera merged commit ec48573 into epic/3150-attachments Nov 4, 2026
43 of 55 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: ui-sdk Wire protocol, runtime schemas, the server and client SDK surfaces. area: ui-server Hono app factory: turn coordinator, auth, session catalog, routes, cron. contract Touches the integration contract: CONTRACT: commit, contract doc updated in the same commit. type: feat A new capability or a change in user-visible behaviour.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant