CedarCopilot wants to merge 2 commits into staging from worktree-agent-a967add8afe5341bc
Live on prod, no production signal yet
Verification reached its budget before a verdict.
Verification reached its 130s outer budget before the verifier returned. Libra is keeping this intent verifying until the next check.
No production telemetry tied to the intended CedarYjsProvider.flush behavior was found since 2026-09-06T17:28:27Z. Exact files.applyUpdate/flush queries returned 0 OTEL spans and CloudWatch searches returned 0 matching logs, so success/error volume, latency, refused=true.
Verification reached its 130s outer budget before the verifier returned. Libra is keeping this intent verifying until the next check.
Verification reached its 130s outer budget before the verifier returned. Libra is keeping this intent verifying until the next check.
Verification reached its 130s outer budget before the verifier returned. Libra is keeping this intent verifying until the next check.
Libra has verdicts on 0 of 7 tracked behaviors on prod; 7 are still being checked. Libra checks hourly for 3 days after each deploy.
PR #2686 taught applyUpdate to report persisted (and rejections) so callers could tell a real save from one a hook refused. writeDocumentTool was updated. The tRPC route the browser calls was not.
files.applyUpdate dropped both fields. A refused save therefore reaches the browser looking exactly like a clean one , a 200 carrying the row's unchanged version/yjsRevision and three empty byte buffers. There is nothing else on the wire that separates the two.
So CedarYjsProvider.flush() took the success path and advanced lastSyncedStateVector to targetSV, cleared dirty, and wrote the new state vector to IndexedDB.
The consequence is worse than a lost save. Because the next update is encoded as Y.encodeStateAsUpdate(ydoc, lastSyncedStateVector), every later delta is computed against a state the server never reached. The refused bytes are not merely dropped , they become unre-sendable: Yjs holds the arriving items as pending structs it cannot integrate, because the items they depend on were never accepted. The tab believes it is in sync, and goes on rendering a document the server does not have and never will.
This also makes a comment in document-saving/types.ts false. It says the client "will re-send the delta on the next forceFlush after the user fixes the errors". It would not have.
Live. Xander's agenda document 134bad3c-f206-41d1-9c62-4f43a6c6763d: the server blob holds 6 rows under "You promised", his browser shows 8 , one of them (2118f67f, "Resend Sep 3 invite and lock in Garcia and Dart") a task whose status is done. The reconciler removes done tasks from the agenda, so its presence in the tab is state the server does not have.
Reachable today, in the playbook editor. playbook.compile is the one hook that returns abortPersist on the human save path (agenda.integrity stopped doing so in #2686 and only ever fired on agent origin). A playbook that fails to compile is refused , and before this PR that refusal was reported as a success. PlaybookDocument.handleFlushEnd set saveState = 'success', and the provider advanced past the refused bytes. Fixing the errors afterwards then saved an incomplete document: everything typed in the refused batch was already stranded.
In a test. Both new provider tests fail on staging and pass here. The first is the one that matters , it replays the second flush's payload onto a fresh Y.Doc and asserts it still carries the refused edit:
ā re-sends refused bytes on the next flush when the server did not persist
expect(serverView.getMap('state').get('refused')).toBe('1')
Expected: "1"
Received: undefined
Server , files.applyUpdate returns persisted and rejections. The mutation's return type is now written out as an exported ApplyUpdateResponse rather than inferred, so dropping either field again is a compile error in files.ts instead of a silent one in the browser. Verified by deleting the line and watching tsc fail with TS2741: Property 'persisted' is missing. This replaces an earlier source-text guard test, which the compiler does strictly better.
Client , on result.persisted === false, CedarYjsProvider:
lastSyncedStateVector where it is, so the refused bytes ride every future delta;dirty true (so hasPendingLocalChanges() and the editor's isDirty stay honest);FlushError carrying error (the rejection reasons), plus hooks and rejections.persisted is optional in the client's wire type on purpose: a tab holding cached JS can outlive the server deploy that added the field, and reading its absence as a refusal would fail every save. Only an explicit false is a refusal.
A refusal is deterministic , re-POSTing identical bytes gets refused identically. So the refusal path deliberately
Libra has not measured any production surfaces for this change yet.