Repository navigation
Conversation
…d-resend Desktop edit-and-resend refused any selected message carrying attachments: the copy excludes the revised turn, so no target-owned refs existed client-side and the edit failed closed (apache#5109). The Host can now stage them: a revision copy carries the edited turn's user-uploaded artifacts into the new Session (the include path built for user uploads, whose sentinel turnId the turn-scoped selection misses) and returns the rewritten refs on the committed response. The shared lifecycle restages the source attachments as retained pending items at edit start, swaps them for the copy's target-owned refs when the prepare succeeds, and re-keys the plate onto the branch child — the same flow quotes already follow (apache#5274). A rewrite missing from the response keeps its source ref, so the Host's session-owned guard fails that submit loudly instead of sending a dangling reference. Cancel drops the edit's own attachments and keeps the user's. Generated-by: GLM-5.3-Flash (ZCode)
|
First-round red was the protocol compatibility epoch guard: the new |
…achments Older peers reject the additional stagedAttachments field in the strict copy-result decoder, so the wire change is observable: 227 -> 228. Generated-by: GLM-5.3-Flash (ZCode)
Astro-Han
left a comment
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
Reviewed exact head ecfbaf69e0b276465e8529b57220e630362a6a90 against main 59f61cf7: 11 files, +371/-60. This copies the selected user turn's uploaded artifacts into a revision target, adds optional stagedAttachments to the committed response, forwards them through Desktop, and replaces the previous attachment-edit refusal with composer restaging and cancellation cleanup.
Three P2 findings are attached inline:
- Desktop connects the new pending-item restorer to an existing
AttachmentRef[]API. The normal root build fails with TS2322 atuse-composer-attachments.ts:58, matching CI. With the actual shared React hook, beginning an attachment edit double-wraps the item: its displayed name/size disappear andtoSubmittedAttachments()returns a pending object where an attachment reference should be. A conversion adapter in the probe preserves the original reference. - Cancellation compares a complete attachment object against just its
.ref, so the edit's own attachment is never removed. The actual revision lifecycle leavessource.txtalongside a newly addednew.txtafter cancel. A reference-key-only control fixes cancellation before preparation; after preparation the rewritten target reference also needs identity tracking, or it is retained on the source draft after the target copy is abandoned. - The Host looks up an uploaded artifact ID in the filesystem-path map. The existing upload API returns
ref.relativePath = record.id;copyConversationArtifacts()maps those throughartifactIds, whilerelativePathsis keyed by the record's physical relative path. Against a real Host over UDS, editing the fixture's uploaded first-turn attachment commits withoutstagedAttachments. Looking inartifactIdsfirst makes that same request return a target-owned reference. Without it, the composer has no replacement reference and the intended edit cannot submit safely.
Validation: fresh installation and dependency patches; the normal root build:test reaches Desktop and fails at the new type mismatch. The original protocol, staged-context, folder-attachment and real two-client Host revision tests pass (29 total). Additional probes use the actual shared attachment hook and revision actions, plus the existing real Host/storage fixture. Three added assertions fail at this head (hook shape and cancellation before/after prepare); the conversion-adapter control passes. Biome on all 11 changed files, ASF headers, whitespace checks, and the protocol epoch guard pass. The head merges cleanly with current main. No packaged Electron or native Desktop end-to-end run was performed, and the renderer build is blocked by the earlier type error.
P3 retry follow-up: please retain or reconstruct the new staged-reference result on the existing-target path (session-revision-coordinator.ts:770). With only the artifact-map lookup corrected in the probe, the first request returns a staged reference but an identical retry returns only the Session projection. This observation uses that lookup control; I am not counting it as an additional independently reproduced P2 on the unmodified head. The new result should remain usable across retries as well as initial creation.
Protocol: the strict response decoder accepts the new field and rejects malformed attachment shapes. Bumping compatibility epoch 227 to 228 is appropriate and passes the guard against current main. Other open PRs also propose 228; after any of them merges, use the next unused epoch and revalidate before merging this one.
CI: 39854063152 fails in the build on the same new TS2322 error reproduced locally. The description's successful Desktop typecheck claim does not hold for this head. The three P2 findings need fixing; this comment does not approve merging.
The revision lifecycle's restorer contract hands the surface PendingAttachment[] items, but the desktop stagedContext bound the shared hook's ref-based restoreAttachments, which re-wraps each entry through retainedToPending: the wiring broke the desktop compile on the pending/ref type mismatch (TS2322), and at runtime every restaged item lost its display name and size and carried its real ref one level down, so the prepare swap and the submit could never match it. The lifecycle tests missed this because their fake restorer appends verbatim. Add restorePendingAttachments(ownerKey, items) to the shared hook — it appends to the owner bucket and registers the staging keys untouched — and bind it as the desktop stagedContext's restorer, covering the prepare re-key and the cancel restore alike. The lifecycle contract is unchanged; a hook-level test pins the verbatim staging the fake could not catch. Review thread: db=4354065450 (apache#6246). Generated-by: GLM-5.3-Flash (ZCode)
clearRevisionStagedContext matched the edit's own attachments by JSON-stringifying whole AttachmentRefs, but the plate items nest the ref under source.attachment, so the shapes never matched and cancelling an edit left its carried attachments staged; after prepare the refs are target-owned anyway, so a source-shape match could never survive the rewrite either. Record the restaged items' staging keys on the draft (editOwnedStagingKeys) — the identity that survives prepare's ref rewrite — and drop by key on cancel. A carried attachment now drops while the user's own additions survive, before and after prepare. Review thread: db=4354065453 (apache#6246). Generated-by: GLM-5.3-Flash (ZCode)
The revision copy built stagedAttachments only from the copy's relativePaths map, but a user upload's ref names the upload's Artifact record id, which copyConversationArtifacts reports under artifactIds — so an uploaded attachment was copied into the new Session and then dropped off the committed response, leaving the client holding its fail-closed source ref and breaking edit-and-resend for uploads (apache#5109). Look the ref up in artifactIds first, matching the transcript rewriter's order, then relativePaths. The two-client UDS test now revises the upload turn itself and requires the rewritten, readable target ref on the committed response. Review thread: db=4354065457 (apache#6246). Generated-by: GLM-5.3-Flash (ZCode)
Astro-Han
left a comment
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
Reviewed the three follow-up commits at 5529fa931e0b33f710db110952667aac3f827ee4 (seven-file increment, +318/-28) after ecfbaf69. All three previous attachment P2 findings are fixed. One new P2 remains: protocol epoch 228 is already used by current main for a different strict wire contract. See the inline comment at the epoch declaration.
The Desktop controller now binds restorePendingAttachments, so pending items retain their display name, size and nested source ref. Cancellation records and removes edit-owned staging keys, which survive the target-ref rewrite, while preserving user-added files. The Host now checks artifactIds before relativePaths; a real uploaded artifact is returned under the target Session and its bytes remain readable.
After fresh dependencies and patches, the normal root test build, production renderer build and complete Desktop typecheck passed, including the previously failing controller type. All 27 protocol/lifecycle/real two-client Host tests passed. An additional actual shared-hook/revision check passed all 22 tests, including its imported lifecycle cases and cancellation before/after prepare. Rebinding only the old restorer reproduces the wrapped attachment failure (21 pass, 1 fail); loading the old cancellation module reproduces four carried-file retention failures (18 pass, 4 fail). A separate real Unix-socket Host probe returns a rewritten attachment on the first request; loading only the old coordinator loses that attachment again. Source files were unchanged for these controls. Seven-file Biome, ASF headers and git diff --check passed.
New P2: main 15d0a0dd19d39e2bc09ae35940e432c636982315 already advertises 228. This PR also advertises 228 while adding stagedAttachments to a closed response decoder. Loading main's actual decoder into the unchanged protocol suite makes the new accepted-response case fail with invalid_frame (5 pass, 1 fail). The actual epoch guard rejects this head against main, and a static merge also conflicts in protocol/index.ts. Rebase while retaining both contracts, choose an unused epoch above main, and run CI on the result. #6195 currently proposes 229 too; that number is not reserved for either PR.
The earlier P3 retry observation remains: repeating the exact same session.revision.create returns committed without stagedAttachments, although the first response includes them. The probe's post-restart response includes an attachment again; that does not establish durable replay of the original response. This follow-up does not demonstrate duplicate user notifications or cross-Session leakage.
There are no current-head CI checks or runs reported for this head. I did not run a packaged Electron edit/resend flow, native platform tests, or a reconciled merge build. Local attachment checks passing do not resolve the protocol P2 or establish current-head CI success.
|
Merged current |
Main landed a sibling 228 (executor catalog filtering) first; this branch's stagedAttachments wire change re-sequences to 229 per the sibling-bump rule. Generated-by: GLM-5.3-Flash (ZCode)
Astro-Han
left a comment
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
Reviewed the merge at b95dfc54d4f2916abb77bb3d51dfedca17437439 against 5529fa93 and current main. The previous epoch P2 is fixed: the branch preserves main's epoch-228 executor-catalog contract and assigns the incompatible staged-attachment response epoch 229. The actual protocol guard passes 228 → 229; retaining 228 makes it fail. All twelve attachment implementation/test files outside the protocol index are byte-identical to the previous reviewed head.
New P2 — update the existing Desktop revision tests for the new attachment lifecycle. Current-head CI 39965827644 fails six tests across app-shell-revision-actions.test.ts, app-shell-revision-resend.test.ts and use-composer-quotes-revision-send.test.ts. The old attachment test still expects editing to be refused, while existing staging fakes omit the attachment restore/clear operations now used unconditionally in preparation (packages/ui/src/revision-staged-context.ts:563, :577). I reproduced 12 passes / 6 failures locally. Loading only main's previous lifecycle module makes all 18 pass. Keeping the new production code and updating only those test fixtures plus the obsolete refusal assertion also makes all 18 pass. These failures establish a test integration regression, not a separate claim that the real Desktop attachment surface lacks those methods.
Fresh dependencies/patches, the normal root build and renderer build passed. All 27 protocol, actual two-client UDS and shared attachment-lifecycle tests passed; protocol-file Biome, ASF headers, diff checks and static merge also passed. The three earlier attachment P2 fixes remain intact. The previous P3 still applies: an exact duplicate revision request can return the committed result without stagedAttachments; this merge does not change that response path. The PR description also still quotes an older epoch.
The PR is now conflict-free but its current CI is red for the confirmed test regression above. Epoch 229 is not reserved: recheck against main after other protocol PRs merge. I did not repeat the previous full attachment negative-control matrix or run a packaged Electron/IME edit flow, and I did not build a separate synthetic merge tree.
… restaging (apache#6246) The staged revision attachments feature (apache#5109) restages a carrying message's attachments on edit instead of refusing it, and prepareRevisionSend now re-keys the composer plates through restoreAttachments/clearAttachments. The UI-layer suite was updated alongside the feature, but the desktop-layer fixtures still modeled the apache#5274-era contract: their fake stagedContext lacked the attachment methods (so prepare threw and returned false), and one test still asserted the old refusal guard. None of this surfaced in CI before, because every earlier head of this branch died on the protocol epoch guard long before the test stage. Fill in restoreAttachments/clearAttachments on the three desktop fixtures, and rewrite the refusal test to assert the restaging semantics already locked in by the UI suite. Generated-by: GLM-5.3-Flash (ZCode)
Astro-Han
left a comment
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
Reviewed 97107422f1021fa96eafdcd4903fe309275fd367, including the full PR diff and the single appended commit since b95dfc54. The increment changes only three Desktop test files (+36/-21): staging fakes now provide the attachment restore/clear methods, and the obsolete attachment-refusal assertion now checks the retained attachment, draft and composer text. All production files are byte-identical to the previously reviewed head.
The previous CI P2 is fixed; I found no new P0–P2 in this increment. With fresh dependencies and dependency patches, the normal clean root build:test passed and all 18 tests in the three affected files passed. Loading only those three test files from b95dfc54, while keeping current production modules, reproduces the same six failures (12 pass / 6 fail). This independently confirms the fixture integration fix. Another 27 protocol, real two-client UDS, attachment lifecycle and attachment-hook tests passed. Changed-file lint, ASF headers, diff checks and the actual protocol guard passed.
The earlier attachment fixes and valid epoch increase (228 → 229) remain intact. The existing P3 retry limitation remains at session-revision-coordinator.ts:775: the committed-target retry response still omits stagedAttachments; this test-only commit does not change that path. Recheck the next unused epoch when integrating other protocol PRs.
Current-head CI 40036945046 passed, and a static merge into current main 15d0a0dd1 is clean. I did not rerun the full Desktop/UI suites, renderer/type checks or the previous full attachment negative-control matrix, and I did not run a packaged Electron/IME edit flow or build the synthetic merge tree. The prior production-code review and its stated limits still apply.
|
Closed the recorded P3 in |
The committed-probe retry path returned a bare committed response, so a client that re-sends an interrupted revision.create never learns the stagedAttachments the first attempt staged (apache#6246 review P3). The retry now re-derives the edited turn's attachments and resolves their copies with `existingTarget: 'reuse_verified'` — idempotent, nothing is re-copied — and answers with the same target-owned refs. The staging helpers move to module scope so the retry path shares them with the primary copy. Generated-by: GLM-5.3-Flash (ZCode)
Astro-Han
left a comment
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
Incrementally reviewed e379bf0ada256df36dae9da64207f31d4011cca9 against 97107422: one appended commit, two files, 115 additions and 37 deletions. The coordinator extracts the attachment-selection helpers and now reconstructs staged attachment refs on the committed-target retry path. The previous P3 omission is fixed while the target artifacts remain present.
P2 — a committed retry can recreate an attachment that another Client explicitly deleted (inline at session-revision-coordinator.ts:762). This path calls the artifact copy writer again after the conversation copy has already committed. existingTarget: 'reuse_verified' verifies an existing artifact, but creates a new copy when that target artifact is absent. It therefore does not make this committed-response reconstruction read-only.
I reproduced this through the real Host with two UDS Clients and actual artifact storage: Client A creates the revision and receives its staged upload; Client B successfully calls artifact.delete on that target upload, and a fresh artifact query confirms an empty target list. Client A then repeats the identical revision request. The upload record reappears under the original target ID, and a real read_text query returns the deleted payload (retained bytes). Loading only the prior coordinator implementation while retaining the current fixture leaves the deletion intact. The regression case fails on this head and passes with that prior implementation. Replay the committed attachment result using durable metadata or a read-only lookup that respects subsequent deletion, rather than invoking a copy operation that can publish artifacts again.
A fresh patched install and the normal clean root build:test passed. All 26 protocol, real two-client revision and shared lifecycle tests passed, as did the 18 Desktop revision tests. Changed-file Biome, diff checks and the protocol guard passed. This increment adds no protocol change beyond the existing valid 228 → 229 increase against main 15d0a0dd1; recheck the next unused epoch at integration. The static merge is clean.
The current-head CI test job was still running at the latest check; its result cannot resolve the independently reproduced deletion regression above. I did not rerun the full workspace suite, renderer build, all Desktop typechecks or the earlier full attachment control matrix, and I did not run a packaged Electron edit flow.
A retry of a committed revision.create answered through the artifact copy writer, whose `reuse_verified` re-creates a target record when it is absent — so a retry republished a staged attachment another client had deleted (apache#6246 review). The landed source-to-target id pairs now persist on the copy header at commit time, and the retry rebuilds its response with read-only `getInSession` lookups: a staged attachment that was deleted after the commit stays deleted and drops off the response, leaving the client's fail-closed gate to mark the edit no longer restageable. The staging helpers move to module scope shared by both paths. Generated-by: GLM-5.3-Flash (ZCode)
Astro-Han
left a comment
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
Incrementally reviewed d99aaae89a6da67ebeb54a52ffe40f29128445eb against e379bf0a: one appended commit, three files, 123 additions and 42 deletions. The copy's committed header now stores the landed source/target artifact pairs, and committed retries use getInSession rather than calling the artifact copy writer again.
The previous P2 attachment-resurrection regression is fixed. I found no new P0–P2 issue in this increment. In an extension of the real two-client UDS fixture, I created the attachment-bearing revision, restarted the Host, reconnected both Clients and repeated the original request. The replay returned the same staged attachment refs, confirming that the mapping survived persistence and restart. The other Client then successfully deleted that target upload, and a fresh artifact query confirmed an empty list. Repeating the identical revision request stayed committed, omitted the deleted attachment and left the list empty. Loading only the prior coordinator implementation reproduces the deleted record and its readable original payload. These checks use the real Host, SQLite, artifact files and Client operations; artifact writes were not mocked.
The new stagedArtifacts field is optional durable header metadata. There is no SQL table change. All 108 existing session-store/SQLite metadata tests passed, and the real restart case above verifies the new metadata's round trip. I did not test opening a State Root written by this head with an older Host binary.
Fresh dependencies and patches, the normal clean root build:test, all 26 protocol/two-client/shared-lifecycle tests, changed-file Biome and diff checks passed. The protocol guard accepts the existing 228 → 229 increase against current main 15d0a0dd1, and a static merge is clean. Recheck the next unused epoch when integrating other protocol changes.
The current-head CI test job is still running; this review does not claim CI success or release readiness. I did not rerun the full Desktop/UI suites, renderer build, all typechecks or the earlier complete attachment control matrix, and I did not run a packaged Electron edit flow. The independent restart/deletion probe specifically covers the user-upload path described above.
Main landed a sibling 229 (session check-in cards) first; this branch's stagedAttachments wire change re-sequences to 230 per the sibling-bump rule. Generated-by: GLM-5.3-Flash (ZCode)
|
Merged current |
Summary
Desktop edit-and-resend refused any selected message carrying attachments: the revision copy stops before the revised turn, so no target-owned refs existed client-side and the edit failed closed. This closes that last #5109 gap by staging the edited turn's artifacts into the copy and re-attaching them through the composer plates, the same flow quotes already follow (#5274).
turnIdthe turn-scoped selection misses) and the committed response carries the rewritten, target-ownedstagedAttachments.@maka/ui): edit start restages the source attachments as retained pending items; a successful prepare swaps their refs for the copy's target-owned ones and re-keys the plate onto the branch child; cancel drops the edit's own entries and keeps the user's. A rewrite missing from the response keeps its source ref, so the Host's session-owned guard fails that submit loudly rather than sending a dangling reference.Verification
session-revision-protocoltests (decode/optional/malformedstagedAttachments)revision-staged-contextlifecycle tests (restage/ref swap/re-key/rewrite-missing)apps/desktopfull typecheck (workspace deps built)npm run format:check(full tree)npm run check:asf-headers--base upstream/main--base upstream/main --head HEADstagedAttachmentsis wire-observable so a compatible-change declaration was not available)AI use
Implemented by GLM-5.3-Flash (ZCode); human-reviewed.
Checklist
Fixes #5109