Fix date inconsistency in CLIENT_SPECIFICATION.md#9
Merged
Conversation
Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Address feedback on IPFS/IPNS relay endpoint details
Fix date inconsistency in CLIENT_SPECIFICATION.md
Jan 18, 2026
FSM1
approved these changes
Jan 18, 2026
FSM1
added a commit
that referenced
this pull request
Jan 18, 2026
* bump version to 1.8.1 and update documentation for IPFS/IPNS relay integration * Fix date inconsistency in TECHNICAL_ARCHITECTURE.md (#7) * Initial plan * Fix date inconsistency in TECHNICAL_ARCHITECTURE.md Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> * Fix date inconsistency in DATA_FLOWS.md frontmatter (#8) * Initial plan * Fix date inconsistency in DATA_FLOWS.md frontmatter Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> * Add missing CID return arrows in IPFS/IPNS sequence diagrams (#6) * Initial plan * Add missing return arrows in IPFS/IPNS sequence diagrams Added `B->>C: Return CID` after all `B->>IPFS: Add metadata, return CID` operations to show that the Backend returns the CID to the Client before the Client signs the IPNS record. The Client needs this CID to create the IPNS record that points to it. Updated 8 sequence diagrams: - File Upload Flow (IPNS publish) - Create Folder (empty folder + update parent) - Rename File/Folder - Move File/Folder (destination + source) - Delete File - Update File Content Bumped version to 1.8.2 Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> * Add missing Backend→Client CID return flows in IPFS/IPNS sequence diagrams (#5) * Initial plan * Add return arrows from Backend to Client in IPFS/IPNS sequence diagrams Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> * Add missing Backend→Client CID return arrows in IPFS sequence diagrams (#10) * Initial plan * Add missing Backend->Client CID return arrows in sequence diagrams Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> * Add missing CID return step in IPNS publish sequence diagrams (#16) * Initial plan * Add missing return CID step in IPNS publish sequences Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> * Fix date inconsistency in API_SPECIFICATION.md (#14) * Initial plan * Fix date inconsistency in API_SPECIFICATION.md Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> * Fix missing YAML frontmatter delimiter in API_SPECIFICATION.md (#15) * Initial plan * Fix YAML frontmatter: add missing opening delimiter to API_SPECIFICATION.md Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> * Fix missing Backend→Client CID return in IPFS/IPNS sequence diagrams (#11) * Initial plan * Fix sequence diagrams: add missing Backend->Client CID return arrows Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> * Fix missing return arrow in IPNS publishing sequence diagram (#13) * Initial plan * Add missing return arrow in IPNS publishing sequence diagram Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> * Fix date inconsistency in CLIENT_SPECIFICATION.md (#9) * Initial plan * Fix date inconsistency in CLIENT_SPECIFICATION.md Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> * Add missing Backend→Client CID return flows in IPFS metadata upload sequences (#12) * Initial plan * Add missing return arrows showing CID returned from backend to client after IPFS metadata upload Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> * Fix documentation inconsistencies and clarify IPFS/IPNS relay architecture (#17) * Initial plan * Fix all PR review feedback items Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> * Improve documentation clarity per code review feedback Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> * Add BASE64 encoding steps in IPNS publish sequences and bump to v1.9.0 (#18) * Initial plan * Add BASE64 encoding steps in IPNS sequences and bump version to 1.9.0 Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> * Fix inconsistent indentation in mermaid sequence diagrams (#19) * Initial plan * Fix mermaid diagram indentation to use consistent 4-space formatting Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
4 tasks
FSM1
added a commit
that referenced
this pull request
Jun 16, 2026
Delete ensureFolderRegistered and all 14 call sites across useDropUpload, useFileOperations, useFileVersions, and useFolderMutations. The SDK requireFolder chokepoint (PR #498) now self-bootstraps folders from the root IPNS key on first mutation, making these web-side pre-seed calls redundant. Also removes the unused FolderNode import from sdk-provider.ts. useFolderNavigation.ts requires no change: the key-unwrap there serves the display/metadata-load path, not SDK seeding. REQ-2 (#9). Typecheck and lint green.
FSM1
added a commit
that referenced
this pull request
Jun 16, 2026
Delete ensureFolderRegistered and all 14 call sites across useDropUpload, useFileOperations, useFileVersions, and useFolderMutations. The SDK requireFolder chokepoint (PR #498) now self-bootstraps folders from the root IPNS key on first mutation, making these web-side pre-seed calls redundant. Also removes the unused FolderNode import from sdk-provider.ts. useFolderNavigation.ts requires no change: the key-unwrap there serves the display/metadata-load path, not SDK seeding. REQ-2 (#9). Typecheck and lint green.
FSM1
added a commit
that referenced
this pull request
Jun 16, 2026
…ame at rest (#500) * docs: add phase 48 — SDK self-bootstrap regression fix and shared-folder consolidation Bundles three pending todos onto one branch plus the PR #498 web-e2e regression: - REQ-1 (P0): fix self-bootstrap loadFolder clobbering fresher folderTree state with a stale IPNS snapshot (regressed main web-e2e, run 27587113911) - REQ-2 (#9): remove redundant web ensureFolderRegistered seeding + useFolderNavigation unwrap, gated on REQ-1 - REQ-3 (#8): route shared-folder writes through the SDK client - REQ-4 (#5 / Phase-14 M1): encrypt share itemName at rest via ECIES Defers CRDT-IPNS-inbox research (#2). REQ-1 acceptance: pre-merge web-e2e via 'gh workflow run web-e2e.yml --ref <branch>'. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: f826ffa63025 * docs(48): research phase domain Entire-Checkpoint: ed421ad6df8c * docs(48): add context and validation strategy Locks REQ-3 (sibling sharedFolderTree by shareId) and the two REQ-4 policy decisions: A2=lazy client backfill of legacy plaintext itemName, A3=include the invite flow. Adds Nyquist VALIDATION.md (REQ-1 acceptance = pre-merge web-e2e dispatch). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: a1ae21343869 * docs(48): create phase plan (6 plans, 3 waves) Entire-Checkpoint: 9b36cdf380e5 * docs(48): add pattern map Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 66a841737a7a * test(48-01): add RED reconcile guard test for loadFolder - Three cases: keep-fresher (RED), absent-loads (green), older-overwritten (green) - Test A confirms the bug: existing sequenceNumber=5n clobbered by stale snapshot=3n - Uses bigint literals and vitest *.test.ts convention per memory Entire-Checkpoint: ebe50165e85d * feat(48-01): add sequence guard to loadFolder to prevent stale clobber - Read existing folderTree entry after IPNS resolve - If existing.sequenceNumber >= result.sequenceNumber, keep existing and skip set() - Emits folder:loaded with existing children/sequenceNumber on the guard path - Absent folders still resolve and set normally (no #498 regression) - Fixes P0: bin-restore and version-restore regressed in main web-e2e run 27587113911 Entire-Checkpoint: 035cb28ff942 * docs(48-01): SUMMARY and STATE for plan 01 checkpoint Tasks 1-2 complete (RED+GREEN); awaiting Task 3 PRE-MERGE web-e2e gate. Entire-Checkpoint: c531516e1121 * fix(48-01): re-resolve bin before auto-repair to prevent empty clobber loadBin treated a null resolveIpnsRecord as 'bin empty' and published an empty record with no expectedSequenceNumber, so a transient cold-cache null after page.reload (with two concurrent loadBin calls) destroyed the real bin record server-side — the deleted item vanished from the bin after reload (bin-restore-after-reload.spec.ts step 6). Re-resolve before the destructive empty publish, and re-resolve after publishing to adopt any real higher-sequence record. SDK-only; 183 SDK tests pass. Unsigned: 1Password SSH signer wedged mid-run (AFK); needs re-sign or admin-merge before landing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 23d236999200 * fix(48-01): guard folder-nav redirect so a stale subfolder resolve cannot bounce an off-route user A subfolder resolve that fails after the user has navigated to another route force-navigated to /files via the catch block, guarded only by latestNavTarget. A react-router nav to /bin does not update that ref, so the redirect bounced the user off /bin back to /files. Add a route-still-here guard using the live pathname before the redirect fires. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 5ef54e2e74a7 * docs(48): REQ-1 gate blocked handoff - bin-restore-after-reload still red Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 8c2131ef9dab * docs(48): correct REQ-1 root cause from trace - bin IPNS 404 auto-repair clobber, not nav Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 86e784543056 * fix(48-01): stop loadBin from clobbering the bin with an empty auto-repair publish loadBin previously published an empty bin record on a double-null resolve via saveBinMetadata with no expectedSequenceNumber; the API then unconditionally incremented the sequence and overwrote latestCid with the empty-bin CID, wiping the real bin after delete+reload (two concurrent loadBin calls could both clobber). The API /ipns/resolve already DB-cache-falls-back, so a client-side null means the record is genuinely absent at that instant - publishing empty on read is never safe. Changes (all client-side, no API change): - loadBin: remove the destructive empty-bin auto-repair publish. On no record, return an in-memory empty BinState (entries=[], sequenceNumber=0) WITHOUT publishing. The first addToBin lazily creates the real record at seq 0+1=1, matching the API create-path. Added a bounded retry (6x ~500ms) to convert a transient null into a successful load. - deleteToBin: self-heal by lazily loading the bin when binState is null. Bin init is fire-and-forget on login, so a delete soon after login/reload previously threw BinNotLoadedError and the web fell back to a HARD delete - the item vanished instead of moving to the bin. This was the actual cause of recycle-bin failures. - client.loadBin: anti-clobber guard - the in-memory empty fallback never overwrites or broadcasts over an already-loaded non-empty bin. Tests: invert the old bin.test that asserted the destructive empty publish to assert loadBin does NOT publish on null; keep the does-not-clobber test; add deleteToBin self-heal + loadBin anti-clobber tests. Full SDK suite green; recycle-bin.spec E2E green 8/8. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 5869de8fb5a5 * fix(48-01): keep isLoading true during session restore to stop protected-route login bounce After a reload, CoreKit re-initializes (coreKitInitialized flips true) a beat before restoreSession sets isLoggingIn/isAuthenticated, leaving a transient where !isLoading && !isAuthenticated are both true. A protected route mounting in that gap (opening /bin right after a reload) hit BinPage's auth guard and redirected /bin -> / -> /files, so the bin never rendered (bin-restore-after-reload.spec.ts step 6). Including (coreKitLoggedIn && !isAuthenticated) in isLoading keeps it true until restore resolves; a genuinely logged-out user has coreKitLoggedIn false so the login redirect still fires. Verified locally: the spec passes end-to-end under NODE_ENV=test (2x). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 8892055ce642 * refactor: remove web folder-seeding now that SDK self-bootstraps Delete ensureFolderRegistered and all 14 call sites across useDropUpload, useFileOperations, useFileVersions, and useFolderMutations. The SDK requireFolder chokepoint (PR #498) now self-bootstraps folders from the root IPNS key on first mutation, making these web-side pre-seed calls redundant. Also removes the unused FolderNode import from sdk-provider.ts. useFolderNavigation.ts requires no change: the key-unwrap there serves the display/metadata-load path, not SDK seeding. REQ-2 (#9). Typecheck and lint green. * docs(48-02): complete REQ-2 web folder-seeding removal plan Entire-Checkpoint: 3cc6e6139aaf * feat: add shared-folder state contracts to SDK - Add SharedFolderState type carrying share-context fields - Add SharedFolderTree keyed by shareId with key-zeroing on delete/clear - Add sharedFolder:updated event member to SdkEvent union Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test: add shared-folder tree isolation and client shared-write tests - SharedFolderTree per-share isolation + key-zeroing (GREEN) - client.uploadToSharedFolder read/delegate/write-back/emit contract (RED) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat: SDK client owns shared-folder state and publish - Add sharedFolderTree field + loadSharedFolder/hasSharedFolder/getSharedFolderState/unloadSharedFolder - Add five shared write methods delegating to share/shared-write.ts via publishWithCas - Each method reads state, builds context, writes back, emits sharedFolder:updated - Export SharedFolderState and SharedFolderTree from index Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: complete 48-03 shared-folder SDK ownership plan Entire-Checkpoint: dc7607abe28a * feat: encrypt share itemName at rest via additive item_name_encrypted column - Add additive nullable item_name_encrypted bytea to shares and share_invites via migration EncryptShareItemName1749200000000 (no data UPDATE; server is zero-knowledge and cannot re-encrypt legacy plaintext rows) - Add itemNameEncrypted entity columns plus hex-validated optional DTO fields on create-share, create-invite, claim-invite and the response DTOs - Persist client-supplied ECIES ciphertext in createShare, createInvite and the invite-claim path; server never encrypts (REQ-4 decision A3) - Extend ECIES round-trip test for UTF-8 itemName and add ciphertext-persist plus no-server-encrypt service assertions - Regenerate @cipherbox/api-client to expose itemNameEncrypted Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: complete 48-05 REQ-4 API itemName encryption plan Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: c873f80a93e3 * feat(web): route shared-folder writes through SDK with event-fed projection - Add shared-folder-projection helpers seedSharedFolder + subscribeSharedFolderProjection - Seed SDK sharedFolderTree on share/subfolder entry and up/breadcrumb depth restore - Subscribe useSharedNavigation refs to sharedFolder:updated as projection-only state - Route all five useSharedWriteOps handlers through getSdkClient shared methods, no write-back - Drop withConflictRetry and buildSharedWriteContext ceremony; SDK owns CAS retry - Add projection unit tests proving per-share isolation and reads-nothing-back Implements REQ-3 web side (phase 48) * docs(48-04): complete shared-folder write projection plan Entire-Checkpoint: fcf515ad5df0 * test(48-06): add failing test for share itemName decrypt and backfill helpers * docs(48-07): plan SDK-owned shared-folder refresh and poller consolidation Entire-Checkpoint: 9f0cd23de3e3 * feat(48-06): ECIES-wrap share itemName on create and decrypt for display - Wrap itemName with recipient pubkey in ShareDialog before createShare; send ciphertext-only (empty plaintext) - Wrap itemName with ephemeral pubkey on invite-create; re-wrap for recipient on claim (decision A3) - Decrypt itemNameEncrypted into the plaintext store projection on received-share load; display sites unchanged - Add decryptItemName + shouldBackfill helpers and lazy-backfill pass for legacy rows (decision A2) * docs(48-06): complete REQ-4 web itemName encryption plan Entire-Checkpoint: 102cf857992f * feat(48-07): add SDK client.refreshSharedFolder for shared-folder re-resolve - refreshSharedFolder(shareId) re-resolves IPNS via sdkCore.loadFolderMetadata - applies the #489 sequence-guard: stale/equal resolve re-emits existing state - adopts a newer sequence into sharedFolderTree and emits sharedFolder:updated - throws Shared folder not loaded on an unloaded shareId - TDD-covered: newer adopt, stale no-clobber, null no-op, unloaded throw * refactor(48-07): route shared poller through SDK refreshSharedFolder - 30s poller calls getSdkClient().refreshSharedFolder(currentShareId) - remove inline refreshFolderContents IPNS resolve + IPFS fetch + decrypt - drop now-unused resolveIpnsRecord/fetchFromIpfs/decryptFolderMetadata imports - projection subscription is now the sole ref writer on write AND poll paths - revocation check unchanged * test(48-07): cover poll-through-SDK refreshSharedFolder projection path - add refreshSharedFolder to the fake SharedFolderClient recording the call - assert the poller calls refreshSharedFolder with the active shareId - assert refs update ONLY via the sharedFolder:updated subscription, not the call - assert a mismatched-shareId refresh event is ignored per-share isolation * docs(48-07): summarize SDK-owned shared-folder refresh and poller consolidation - record Tasks 1-4 commits, verify results, and dist-rebuild confirmation - Task 5 shared-folder sync UAT deferred to end-of-phase web-e2e * fix(web): seed SDK shared-folder state when opening a write-shared file The recipient save path routes through client.updateSharedFile (REQ-3, 48-04), which calls requireSharedFolder and throws 'Shared folder not loaded' when the share is absent from the SDK sharedFolderTree. The folder-open branch seeds the tree, but the standalone file-share branch did not — so a recipient's Ctrl+S on a write-shared file threw, the editor never closed, and writable-shares.spec.ts '10.3 Bob opens the write-shared file and can edit it' timed out at waitFor hidden. Seed the file branch (children: [], sequenceNumber: 0n; updateSharedFile is a file-only publish that reads folderKey + owner/recipient pubkeys only and ignores children/sequence). Only write shares (IPNS key present) seed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: c89f6e96802a * fix(api): allow empty itemName on create-invite to match create-share REQ-4 (48-06) moved the share display name into the ECIES itemNameEncrypted ciphertext and changed the web client to POST itemName: '' on create. The sibling CreateShareDto was relaxed to accept this, but CreateInviteDto kept @minlength(1) on itemName, so the global ValidationPipe (whitelist + forbidNonWhitelisted) rejected invite-link creation with HTTP 400 — failing invite-link-workflow.spec.ts '3.1 Alice creates an invite link for a file' at waitForSuccess. Drop @minlength(1) to mirror CreateShareDto. No OpenAPI/client change: the Nest swagger plugin does not emit minLength, and the generated client source is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: e0689f5cec4f * refactor(48): drop dead shared-write folderKey prop, redundant 0x-strip, stale doc Simplify-pass cleanups (no behavior change): - useSharedWriteOps: remove the unused folderKey param (the SDK owns the folder key via seeded sharedFolderTree state) and its call-site wiring. - shared-folder-projection.parsePublicKey: drop the redundant 0x-strip; hexToBytes already strips an optional 0x prefix. - client.ts getFolderIpnsPrivateKey: fix a doc comment that referenced ensureFolderRegistered, which this branch deleted. Deferred (noted, out of scope for this PR): SharedFolderTree is keyed by shareId so the web re-seeds per nav depth (deeper fix = a shared ensureFolderLoaded analog); serial DFS in ensureFolderLoaded; pre-existing share-key cache bypass in updateSharedFileHandler. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 5cdb4df5efb2 * test(48-05): cover invite/claim itemNameEncrypted ciphertext persistence Add 3 behavioral tests to share-invite.service.spec.ts: - createInvite: persists hex ciphertext as Buffer passthrough (no server encryption) - createInvite: persists null for legacy clients that omit itemNameEncrypted - claimInvite: re-wrapped itemNameEncrypted from DTO is stored as Buffer on the Share Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Entire-Checkpoint: a49ce185baba * docs(48): mark phase nyquist-compliant after filling invite ciphertext test gap Update 48-VALIDATION.md: real per-task verification map (REQ-1..4 all green via unit + web-e2e), Wave 0 checked, sign-off approved, audit trail (1 gap found, 1 resolved). The gap — invite/claim itemNameEncrypted persistence — was closed by share-invite.service.spec.ts (commit e8a3a2fe0). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 42e4f8b5b5a4 * fix(api): widen itemNameEncrypted MaxLength to 2500 for worst-case names The 1024-char cap was copied from the fixed-size encryptedKey field, but itemNameEncrypted wraps a variable-length display name: a 255-char multibyte name (CJK/emoji) is up to ~765 UTF-8 bytes, whose ECIES ciphertext is ~1724 hex chars — exceeding 1024 and triggering a 400 (same class as the create-invite MinLength regression). Raise the cap to 2500 across create-share, create-invite, and claim-invite. No OpenAPI/client change (the swagger plugin does not emit maxLength). Flagged by CodeRabbit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: aa221dedc616 * fix(web): harden shared itemName decrypt and unload SDK keys on unmount Two review findings on the REQ-3/REQ-4 shared paths: - fetchReceivedShares decrypted itemName inside Promise.all, so one corrupt / wrong-key / truncated ciphertext row rejected the WHOLE received-shares page. Guard per row, degrading just that row to its plaintext fallback (security review, MEDIUM availability). - The sharedFolder:updated projection effect only unsubscribed on unmount; it never unloaded the SDK's cloned shared-folder state, so folderKey + ipnsPrivateKey lingered in SDK memory after leaving a share. Call unloadSharedFolder(activeShareId) in cleanup to zero them (CodeRabbit). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 19d891c11b3e * docs(48): add phase 48 security review report Two-agent security review of the REQ-4 itemName at-rest encryption and REQ-3 shared-folder key handling. Overall risk LOW: zero-knowledge invariant holds (server stores ciphertext verbatim, never encrypts/decrypts), crypto correct, key-zeroing + cross-share isolation hold. One MEDIUM (received-shares decrypt guard) fixed; LOW items deferred with rationale. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 1a497a44e1ab * chore: sync Cargo.lock to crate versions 0.6.0 for fuse and sdk The cipherbox-fuse and cipherbox-sdk crate Cargo.toml files are at 0.6.0 but the committed lockfile still pinned 0.5.3 / 0.5.0. Regenerate the lock to match. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 17ebf9d4ebfa * chore(release): set release targets for PR #500 * test(48): cover itemNameEncrypted present/null branches in invite controllers The new itemNameEncrypted hex-or-null ternaries in share-invites.controller and invites.controller added per-file branches whose present-vs-null sides were not both exercised, dropping branch coverage below threshold (63.88% < 65% and 73.33% < 74%). Add explicit tests for the present (Buffer to hex string) and absent (null) branches in createInvite, listInvites, and getInviteData, and set itemNameEncrypted on the base mocks. Both files now clear their branch thresholds (69.44% and 76.66%). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: a5866cb6aa14 * test(48): update sdk-e2e bin suite to the self-healing bin contract Commit 73c9f03 intentionally changed the bin contract: loadBin returns an in-memory empty state at sequenceNumber 0 WITHOUT publishing (publishing an empty record on a null resolve is destructive), and the client bin methods self-heal or lazy-load instead of throwing BinNotLoadedError. The sdk-e2e suite still asserted the old contract. bin-operations.test.ts: - fresh-account loadBin now expects sequenceNumber 0 (was 1). - "BinNotLoadedError when bin not loaded" becomes a self-heal test: deleteToBin lazily loads the bin and self-bootstraps the root, then fails only on the missing child with 'Item not found'. error-cases.test.ts (bin ops without an explicit loadBin): - deleteToBin self-heals and fails with 'Item not found' (not BinNotLoadedError). - restoreFromBin / permanentDelete reach the entry lookup (binState lazily loaded empty by the prior deleteToBin) and fail with 'Bin entry not found'. - emptyBin resolves on the already-loaded empty bin. Verified against the source-of-truth unit tests (packages/sdk/src/__tests__/ bin.test.ts, green) and the client self-heal logic in client.ts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 7b06e5dedd10 * fix(sdk): guard shared-folder adopt and emit against unload race, zero stale keys Two CodeRabbit findings on the SDK shared-folder state: - adoptSharedFolderResult and the updateSharedFile / refreshSharedFolder emit sites used a pre-await state snapshot, so a write/refresh completing AFTER unloadSharedFolder(shareId) (now triggered on unmount) could resurrect the unloaded entry and emit sharedFolder:updated for it. Re-read live state right before write-back/emit and no-op if absent; callers pass shareId. - SharedFolderTree.set now zeroes the previous entry's folderKey/ipnsPrivateKey before overwriting so stale key material doesn't linger across re-seeds — guarded on reference identity so the adopt path (which spreads the live entry's same buffers) never zeroes the key it is about to clone. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 45d6a1f9e98b * fix(web): disable no-op itemName backfill and wipe plaintext name buffers Two CodeRabbit findings on the REQ-4 share itemName paths: - backfillSentShareItemNames re-wrapped every legacy row via ECIES on each owner share-list load but cannot persist (no PATCH endpoint, T-48-18) — wasted CPU with no durable effect. Disable the invocation until the update endpoint exists; the function is kept ready for one-line wiring. - ShareDialog and invite.service encoded the plaintext display name into a Uint8Array and wrapped it without clearing the temporary bytes. Wrap in try/finally and fill(0) the encoded buffer, per the clear-sensitive-data rule. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: d9b1db6d1ed0 --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
FSM1
added a commit
that referenced
this pull request
Jun 30, 2026
#6 fail closed when unsealed node has no recoverable write body, preventing a read-only node from being promoted to write-capable without key proof. #2+#7 zero each child newWriteKey immediately after sealChildWriteKey (inline in the map callback) and add a post-Promise.all defensive sweep for any unclaimed child results — D-09 terminal-owner rule. #8 check createAndPublishIpnsRecord return value success flag before firing tombstones or grant mutations so a non-throwing rejection cannot leave the write plane inconsistent. #9 assert rootResult.nodeId matches caller-supplied rootNodeId before grant mutations to prevent rewrapping grants for a different node. #10 wrap tombstone and grant callbacks in try/finally so the root write key is zeroed even when a callback throws — D-09 terminal-owner rule. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 tasks
FSM1
added a commit
that referenced
this pull request
Jun 30, 2026
#583) * docs(65): capture phase context Entire-Checkpoint: aed8bf6fa830 * docs(state): record phase 65 context session Entire-Checkpoint: f49b3b2fe198 * docs(phase-65): research write-chain, bin re-link, and invite claim Entire-Checkpoint: ef5c6ef6e756 * docs(65): add validation strategy Entire-Checkpoint: fe7e071404d5 * docs(65): map phase patterns Entire-Checkpoint: 3c9673822b65 * docs(65): create phase 65 write-chain plan (7 plans, 4 waves) WRITE-01 write-body Ed25519 under writeKey (role 0x04), WRITE-02 full Ed25519 write-revocation (child-first cascade), WRITE-03 co-writer re-wrap + offline error, WRITE-04 tombstone-intent; bin re-link + invite re-wrap (Success Criterion 4). Mock-seam discipline (D-02); sdk-e2e round-trip gate (D-04). OQ-1 (BinEntry.nodeReadKey) and OQ-2 (child-first cascade) resolved. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NUSYqMyCLbNGDLcn1cp1eh Entire-Checkpoint: 9638c8677236 * docs(65): address plan-checker warnings — surface e2e typecheck and mark research OQs resolved Entire-Checkpoint: e025a94f56f3 * docs(65): record planning complete and annotate roadmap waves Entire-Checkpoint: 7a6909081de9 * docs(phase-65): begin phase execution tracking Entire-Checkpoint: 2a373269bfea * test(65-01): add failing role-0x04 write-chain seal KAT and round-trip - round-trip: sealChildWriteKey then unsealChildWriteKey recovers 32-byte key - role isolation: role-0x02 blob rejected by role-0x04 unseal and vice versa - AAD binding: childId / childKind / childGeneration / wrong key all throw - terminal-owner: input buffers unchanged after both calls (D-09 / T-65-04) * feat(65-01): implement sealChildWriteKey and unsealChildWriteKey with role 0x04 - verbatim copy of sealChildReadKey / unsealChildReadKey with role byte 0x02 → 0x04 - AAD: buildNodeAad(childId, kindByte, childGeneration, 0x04) — child-writekey role frozen - no new imports: reuses sealAesGcmAad / unsealAesGcmAad / buildNodeAad from @cipherbox/crypto - D-09 terminal-owner: neither function zeros caller-supplied buffers - exported from packages/core/src/node/index.ts and packages/core/src/index.ts - all 9 role-0x04 tests green (round-trip, cross-role rejection, AAD-mismatch, terminal-owner) * test(65-03): add failing claimInvite service-flow test - RED: claimInvite import fails — function does not exist yet - Asserts getInviteDataFn called once per claim - Asserts insertShareFn called once with no encryptedChildKeys - Asserts two independent claims of same invite yield two separate grants * docs(65-01): complete plan summary for role-0x04 write-chain seal primitives * feat(65-03): implement claimInvite service flow and barrel export - Add claimInvite to packages/sdk-core/src/share/grant.ts composing claimInviteReadKey with injected getInviteDataFn and insertShareFn callbacks - Re-export claimInvite from packages/sdk-core/src/share/index.ts - One re-wrapped root readKey per claimer, no per-child key fan-out - Zero sdk-layer consumption of the fan-out path confirmed by grep gate * docs(65-03): complete invite-claim service flow plan summary * test(65-02): add failing bin re-link spec for addToBin and restoreFromBin - add nodeReadKey and nodeIpnsName fields to BinEntry in @cipherbox/core - un-skip addToBin and restoreFromBin describe blocks - remove legacy originalFolderKeyEncrypted and FolderChild references - add 4 addToBin tests covering happy-path, revoke ordering, abort-on-revoke-fail, and not-loaded guard - add 5 restoreFromBin tests including AEAD asymmetry proof via real sealChildReadKey - switch vi.mock to importOriginal spread keeping real AES-GCM primitives for asymmetry test * feat(65-02): implement addToBin and restoreFromBin as pure key re-link addToBin: - resolve child IPNS record to extract plaintext PublishedNode id and kind - unseal nodeReadKey from source parent folderKey via unsealChildReadKey - revoke shares before destructive folder mutation (fail-closed ordering) - capture nodeReadKey and nodeIpnsName on BinEntry for restore restoreFromBin: - re-seal entry.nodeReadKey under destination folderKey via sealChildReadKey (role 0x02) - build SealedChildRef with re-linked readKeySealed (no content re-encryption) - add restored ref to target folder and publish; remove entry from bin also fix test fixture: use valid UUID for nodeRef.id to satisfy uuidToBytes validator; replace vi.restoreAllMocks with targeted sealSpy.mockRestore to prevent resetting module-level vi.fn mocks (deriveBinIpnsKeypair) in subsequent test cases * docs(65-02): complete bin re-link plan * docs(phase-65): update tracking after wave 1 Entire-Checkpoint: 02004cb54e8b * test(65-05): add failing write-body-reseal tests for rotateOne write-plane preservation - Tests 1-4: assert unsealNode called with nodeWriteKey, sealNode receives real writeKey not all-zeros placeholder, writeBody.ipnsPrivateKey preserved post-rotation - Tests 5-6: fail-closed guard -- writeSealed present without valid writeKey throws - Test 7: nodeKeySource.writeKey threads through BFS to sealNode - All 6 write-plane assertions fail RED against placeholder engine * feat(65-05): thread real writeKey through rotateOne, remove PLACEHOLDER_WRITE_KEY - Add nodeWriteKey to RotateOneParams and nodeKeySource return type - unsealNode now called with nodeWriteKey to recover write-body on rotation - Fail-closed guard: throws when published.writeSealed present but nodeWriteKey is absent or all-zeros, mirroring the IPNS-key guard pattern - writeKeyForReseal = node.writeBody ? nodeWriteKey : empty; used at all three sealNode sites: main reseal and both CAS-409 merge paths - PLACEHOLDER_WRITE_KEY deleted from all three sites - rotateReadFromNode BFS threads nodeKeySource.writeKey at root, child, grandchild and dirty-resume enqueue sites - Existing IPNS fail-closed guard retained unchanged - Folds FLAG-63-U1 / rotateone-placeholder-writekey-phase65 * docs(65-05): complete write-body-reseal plan summary * feat(65-04): implement shared-write on write-body model with WRITE-01 security and WRITE-03 offline error - Reshape SharedWriteContext: writeKey + readKey + publishedNode replaces raw folderKey and ipnsPrivateKey fields - Add publishNodeFn and addToIpfsFn transport seams for mock-testable operations - Add buildChildWriteLink helper sealing child writeKey under parent writeKey via role 0x04 - Add walkChildWriteKey helper walking the write chain via unsealChildWriteKey - Prove WRITE-01 security: unsealNode without writeKey returns no writeBody; read-only holder cannot reach ipnsPrivateKey - Prove NODE-03: SealedChildRef carries no write field; write link lives only in parent writeBody.writeChildren - Implement createSharedSubfolder: mint child keypair, build write-body, seal, publish, add SealedChildRef + WriteChildRef to parent - Implement uploadToSharedFolder: same pattern for file node with AES-256-GCM content encryption via addToIpfsFn - Implement renameInSharedFolder: mutate read-body child name, preserve writeChildren, re-publish parent - Implement deleteFromSharedFolder: remove from children and writeChildren by IPNS name, re-publish parent - Implement updateSharedFile: caller-supplied fileReadKey + fileWriteKey + fileIpnsPrivateKey, re-seal + re-publish - Implement moveInSharedFolder: re-seal child readKey under dest readKey via sealChildReadKey, walk write chain for writeKey re-link - Add CannotWriteUntilRefetchError with stable code CANNOT_WRITE_UNTIL_REFETCH for tombstoned publish targets - addShareKeysFn never invoked across all six operations - All 29 shared-write tests pass including WRITE-01 security, write-link round-trip, and WRITE-03 tombstone tests * docs(65-04): fix stale Phase-65 convention comments in shared-write WriteChildRef.childId is a UUID from crypto.randomUUID matching Node.id, not the IPNS name. Clarify deleteFromSharedFolder itemId semantics and remove incorrect module-header annotation. * docs(65-04): complete plan 04 shared-write write-body model * fix(65-04): reconcile client SharedWriteContext consumers with write-body model * docs(phase-65): update tracking after wave 2 Entire-Checkpoint: 0c43e1d2e603 * test(65-06): add failing write-revocation driver contract test - Specifies rotateWriteFromNode behavior: per-node new Ed25519 keypair + k51 name + writeKey - Asserts child-first cascade, first-publish at 1n, tombstone-intent teeUnenrollFn calls - Asserts co-writer re-wrap via wrapKey and revoked grant drop via deleteWriteGrantFn - Asserts read-plane invariance: no generation bump, no new readKey minted - Asserts parent SealedChildRef.ipnsName re-pointed to new child name - Tests RED because rotateWriteFromNode is not yet exported from rotation engine * feat(65-06): implement rotateWriteFromNode with child-first write-revocation cascade - Exports WriteRevocationCallbacks type and rotateWriteFromNode function from rotation engine - Child-first bottom-up traversal: leaves get new Ed25519 keypair + k51 name + writeKey first - First-publishes each new k51 name at sequenceNumber 1n via createAndPublishIpnsRecord - Fires teeUnenrollFn for each old name after the new name is published - Re-seals write-body with new ipnsPrivateKey and re-pointed writeChildren under new parent writeKey - Updates parent SealedChildRef.ipnsName to new child name while leaving readKeySealed unchanged - Handles co-writer grants: wrapKey for survivors, deleteWriteGrantFn for revoked recipients - Read plane invariant: no readKey minted, no generation bump on any node - Zeros minted writeKey and Ed25519 seeds on failure paths only (D-09 / Pitfall 4) - Adds createAndPublishIpnsRecord and sealChildWriteKey/unsealChildWriteKey to engine imports - All 70 rotation suite tests pass including 8 new write-revocation tests * docs(65-06): complete write-revocation plan summary * docs(phase-65): update tracking after wave 3 Entire-Checkpoint: 1669fd50ff58 * feat(65-07): export rotateWriteFromNode and WriteRevocationCallbacks from sdk-core - Adds rotateWriteFromNode to the sdk-core top-level barrel so the sdk-e2e suite can import it via the package name rather than an internal subpath (Rule 3 fix: missing export blocked the D-04 gate test) - Adds WriteRevocationCallbacks type export alongside the function * test(65-07): add D-04 write-chain rotation E2E suite - Creates tests/sdk-e2e/src/suites/write-chain-rotation.test.ts as the D-04 phase gate for WRITE-02/03/04 against the live docker API stack - publishWriteCapableNode helper seals folder nodes with real write-bodies and first-publishes each new k51 name at sequenceNumber 1n - Test 1: builds a 2-level subtree (root + child), asserts pre-rotation baseline: write-body unseals under writeKey; parent write link unseals to child writeKey; generation 0 for both nodes - Test 2: drives rotateWriteFromNode with vi.fn() callbacks, asserts WRITE-02 (new k51 names + parent re-point cascade), WRITE-04 (teeUnenrollFn per old name), WRITE-03 (survivor re-wrap unwraps with bob private key; revoked grant dropped), and read-plane invariance (generation unchanged) - Derives new IPNS names from captured getRandomValues spy in child-first order (child-ed25519, child-writeKey, root-ed25519, root-writeKey) * docs(65-07): complete write-chain rotation plan summary * docs(phase-65): update tracking after wave 4 Entire-Checkpoint: c40a8c198c21 * docs(phase-65): complete phase execution Entire-Checkpoint: 77baeecc7244 * docs(phase-65): close rotateone-placeholder-writekey todo resolved by plan 65-05 Entire-Checkpoint: f69e0ef17961 * docs(phase-66): re-tag bin-pin-leak todo to phase 66 where apps/api/shares is in scope Entire-Checkpoint: f7bf47fb6251 * docs(65): security audit — all 29 threats closed Static analysis of Plans 01-07 threat models: 29 mitigations verified in implementation (seal.ts role-0x04, engine.ts writeKey threading + PLACEHOLDER removal, shared-write.ts write-body model, bin/index.ts pure re-link, grant.ts claimInvite). Two accepted risks (T-65-07 Q3 sub-share, T-65-09 invite link) documented. threats_open=0. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs(65): nyquist validation audit — fill coverage map, mark compliant Retroactive audit of phase 65 validation gaps. Maps all 15 tasks across 7 plans to their concrete test files and commands. Marks nyquist_compliant: true; records the one explicitly-deferred item (WRITE-04 live publish-gate) with Phase 66 evidence. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Entire-Checkpoint: 4f25369a581a * docs(phase-68): defer shared-write context publishedNode wiring from phase-65 coderabbit review Entire-Checkpoint: 9406964c08bd * fix(65): resolve CR cluster-B findings in shared-write - F1 zeroization: fill(0) minted key buffers before null on success paths in createSharedSubfolder, uploadToSharedFolder, and updateSharedFile - F2 childId alignment: add fileNodeId param to updateSharedFile so Node.id stays aligned with WriteChildRef.childId across content updates - F3 publish order: publish dest before src in moveInSharedFolder so a partial failure leaves a duplicate rather than an orphaned item - F4 sequence number: pass swCtx.sequenceNumber + 1n in resealAndPublishParent so parent updates use the correct next sequence - F5 fail-closed: throw when neither childWriteKey nor a walkable src write link is available; zero derived key in finally (D-09) Tests: update callers for new fileNodeId param; add fail-closed test for moveInSharedFolder; 36 tests pass (was 35) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(65): fail-closed restoreFromBin and reorder addToBin for atomicity - restoreFromBin: throw instead of defaulting nodeIpnsName/nodeRef to empty values — prevents sealChildReadKey binding to wrong AAD - addToBin: persist bin entry before the destructive folder publish so a failed save cannot orphan the item with no restore key - grant.ts claimInvite: reject empty/whitespace inviteToken, rootNodeId, rootIpnsName before any I/O or crypto work Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(65): make revocation-failure test self-contained Add setupAddToBinMocks() to stub resolveIpnsRecord and fetchFromIpfs so the test reaches the revocation step independently of prior test cases. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(65): defer tombstones post-subtree, tighten key lifecycle, fix childKey zeroing Findings addressed: F5 CRITICAL, F6 major, F7 major, F4 major. F5: move teeUnenrollFn calls from inside rotateWriteSubtree to after the full subtree rotation in rotateWriteFromNode. Adds pendingTombstones[] collection so a failed ancestor publish cannot leave the TEE with a unenrolled child name the parent still references. F6: remove unused newIpnsPrivateKey from WriteRotationResult; zero newKeypair.privateKey immediately after createAndPublishIpnsRecord; zero rootResult.newWriteKey at end of rotateWriteFromNode after grants loop. F7: wrap both unsealChildReadKey and unsealChildWriteKey in a single try/finally so childReadKey is zeroed even when unsealChildWriteKey throws before childReadKey enters the inner try scope. F4: change rotateWriteFromNode return type from Promise<void> to Promise<{ newRootIpnsName: string }> so callers can persist the new root IPNS pointer alongside the grant descriptor. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(65): add missing core mocks, strengthen write-key assertions in rotation unit tests Findings addressed: F3 major, F1 minor, F8 minor. F3: add sealChildWriteKey and unsealChildWriteKey to the @cipherbox/core mock in write-body-reseal.test.ts so the module does not silently export undefined for these symbols if the write-rotation code path is hit. F1: assert sealChildWriteKey call arguments in write-revocation Test 8 -- child write key identity, parent write key shape, and AAD binding args. F8: strengthen write-body-reseal Test 7 to assert both NODE_WRITE_KEY and CHILD_WRITE_KEY appear per-node in the BFS; remove the .catch that was masking rotation errors in GREEN. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(65): zero captured Ed25519 seeds early and verify survivor write key opens write body Findings addressed: F10 minor, F11 major. F10: call clearCapturedKeys immediately after deriving new IPNS names so Ed25519 seed material does not linger in the spy array until afterAll. F11: after unwrapping the survivor write-descriptor with bob private key, call unsealNode on the new root with the unwrapped key and assert writeBody is present; proves the persisted descriptor is the actual new root write key and not merely some 32-byte value. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs(65): defer write-chain e2e seed index-stability hardening from coderabbit review Entire-Checkpoint: 0a6ec17e80ef * fix(65): deleteFromSharedFolder write-body filter uses UUID not ipnsName WriteChildRef.childId is a UUID minted at creation time; filtering by params.itemId (an IPNS name) never matched, leaving a stale WriteChildRef that later crashed rotateWriteFromNode. Add childNodeId param to deleteFromSharedFolder (mirroring moveInSharedFolder) so read-body is filtered by ipnsName and write-body by UUID. Also extend updateSharedFile with optional originalCreatedAt / originalVersions so callers can preserve immutable metadata; Phase-68 will supply prior values. Update client.deleteFromSharedFolder signature to thread childNodeId through. Remove the misleading "childId === ipnsName" module-level comment. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(65): cover write-body WriteChildRef removal on delete Add a test that seeds the parent write-body with a WriteChildRef, calls deleteFromSharedFolder with both itemId and childNodeId, then unseals the republished parent envelope and asserts writeBody.writeChildren is empty. Extend buildSealedParent to accept optional writeChildren so tests can seed real write-body state. Update existing delete tests to supply the new required childNodeId param. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(65): harden write-rotation engine against security edge cases #6 fail closed when unsealed node has no recoverable write body, preventing a read-only node from being promoted to write-capable without key proof. #2+#7 zero each child newWriteKey immediately after sealChildWriteKey (inline in the map callback) and add a post-Promise.all defensive sweep for any unclaimed child results — D-09 terminal-owner rule. #8 check createAndPublishIpnsRecord return value success flag before firing tombstones or grant mutations so a non-throwing rejection cannot leave the write plane inconsistent. #9 assert rootResult.nodeId matches caller-supplied rootNodeId before grant mutations to prevent rewrapping grants for a different node. #10 wrap tombstone and grant callbacks in try/finally so the root write key is zeroed even when a callback throws — D-09 terminal-owner rule. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(65): harden rotation test hygiene for zeroed buffer handling #4 remove swallowed .catch handlers in write-body-reseal green-path tests — RED-phase workaround is no longer needed; let failures surface. #5 return fresh Uint8Array copies from crypto mocks in write-revocation tests so engine fill(0) zeroisation does not mutate shared fixture constants and pollute subsequent tests. #5 same treatment for generateRandomBytes write key mocks. Test 8 in write-revocation now captures fresh copies of sealChildWriteKey arguments via a custom mock implementation, since the engine zeroes the child write key after sealing. #20 zero the minted IPNS private key in publishWriteCapableNode e2e helper after publish in a finally block, narrowing the return type to only ipnsName. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(65): address coderabbit round-2 findings for bin and grant - FIX #16 restoreFromBin: check target folder for existing child by ipnsName before appending restoredItem so a retry after saveBinMetadata failure cannot duplicate the restored child ref. - FIX #11 claimInvite: snapshot ephemeralPrivateKey and claimerPublicKey buffers before the first await so a caller zeroing the inputs mid-await cannot corrupt the subsequent re-wrap; zero the owned copy in finally. - FIX #12 claimInvite: trim rootNodeId/rootIpnsName before using and persisting them so whitespace-padded inputs are not stored as-is. - test(65): add idempotency test for restoreFromBin, trim-input test and snapshot-before-await test for claimInvite; update toBe → toStrictEqual for recipientPublicKey (now a snapshotted copy). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs(phase-68): add SDK envelope-return enablers from coderabbit PR review Entire-Checkpoint: 4710f9a40648 * fix(65): hex-encode bin nodeReadKey on the wire so restoreFromBin survives JSON round-trip Entire-Checkpoint: 19c3208fe45e * fix(65): fail closed on non-32-byte bin nodeReadKey and missing deleteFromSharedFolder childNodeId Entire-Checkpoint: c7f2d3a74cdf * fix(65): route shared-write content uploads through pinWithMode to honor BYO pinning Entire-Checkpoint: e99a4ebfa963 * fix(65): fail closed on non-throwing IPNS publish rejection in publishNodeFn The shared-write publishNodeFn closure ignored pubResult.success, so a 2xx relay response carrying success:false would proceed as if the node were committed — leaving the parent's SealedChildRef pointing at an IPNS name that was never published. Guard on success and throw, mirroring rotateWriteSubtree. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 9e04bdf55b05 * fix(65): use childRef.generation for bin AAD, not stale envelope generation addToBin reconstructed the child-readkey AAD from publishedNode.generation, sourced from an independent IPNS resolve of the child. A stale-CID serve makes that diverge from childRef.generation (the parent mirror the readKeySealed blob was sealed under), so unsealChildReadKey fails GCM auth closed even when the parent folder state is current. Pass childRef.generation per the §2.6 generation-source rule, matching moveItem and navigate.ts. Also capture nodeRef.generation from childRef so restoreFromBin re-seals under the same generation as the unsealed key. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 1af3242d5c34 --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The YAML frontmatter declared
last_updated: 2026-01-18while the document body still showed**Last Updated:** January 17, 2026.Changes
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.