Skip to content

fix(collaboration): harden awareness runtime contracts - #167

Draft
seonghobae wants to merge 54 commits into
mainfrom
fix/collaboration-awareness-bounds-20260811
Draft

fix(collaboration): harden awareness runtime contracts#167
seonghobae wants to merge 54 commits into
mainfrom
fix/collaboration-awareness-bounds-20260811

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Closes #208.
Closes #297.
Closes #305.
Closes #309.
Closes #370.

Buyer-visible boundary

Inkspan publishes local collaboration identity/presence into Yjs awareness and publicly exports collaboration presentation helpers. This Draft hardens that finite local boundary without acquiring provider transport, room authorization, tenancy, persistence, credentials, network, model, database, deployment, retention, durable-audit, or Yjs document authority. Remote awareness remains untrusted presentation data and never authorization evidence.

Current exact boundary

Protected shipped truth is independently resolved as main@3b38ead2d00f44eb578d0689087b9293b3dabe1e. Current exact Draft head is d8407c5fc65027233c880cae2d1f2d14d5261d91 on fix/collaboration-awareness-bounds-20260811. GitHub reports the Draft mechanically mergeable.

Fresh exact comparison from protected main reports this long-lived branch as diverged, 54 ahead / 29 behind, with merge base 50ac98cfa0ad9e8dd75f93ca437a5679fed4d804 and 19 awareness/docs/test paths. The 29 protected-main commits after that merge base include release/control-plane authority this branch does not own. Do not merge, force-push, destructively rebase, or treat this branch tree as current protected-main authority while #118 owns the release boundary. Reconciliation must remain non-destructive and source-owner aware after that boundary permits it.

Implemented

  • trims and Unicode-bounds local display names before awareness publication and applies the same bounded rendering rule to remote labels;
  • rejects blank, numeric-only, and over-80-code-point public collaborator identifiers without proportional Array.from(...) materialization;
  • rejects remote awareness identifier source strings above 1,024 UTF-16 code units before trim() normalization while preserving the normalized in-bound identifier contract;
  • rejects local userId, displayName, and cursorColor source strings above 1,024 UTF-16 code units before normalization and validates their runtime types;
  • contains hostile local identity getters and normalizes them to stable payload-redacted Inkspan errors;
  • preserves undefined -> Collaboration ready and the four declared connection states while invalid runtime status tokens fail closed;
  • public contrastingTextColor() accepts exactly six-digit hexadecimal CSS colors and rejects malformed runtime values before channel parsing;
  • remote collaborator counting reads only own enumerable data properties for remote user / id metadata and contains failures from host-owned awareness.clientID and awareness.getStates(), returning the safe zero-presence fallback rather than leaking a caller/private exception;
  • remote cursor/selection presentation applies descriptor-only reads to name and color, source-bounds remote cursor names, and restricts colors to the existing six-digit grammar plus safe fallback;
  • provider awareness acquisition and structural capability inspection share one module-owned fail-closed boundary, so caller/private exceptions from provider.awareness or awareness members cannot escape configuration/scoped-provider operations;
  • scoped-provider on(...) commits local wrapper state only after host registration succeeds, redacts host registration failures, and permits a clean retry instead of recording a false-success registration;
  • scoped-provider direct off(...) redacts host removal failures and retains local wrapper state after a rejected host removal so callers can retry without losing cleanup authority;
  • scoped-provider disposal attempts every registered listener detachment even if one host-owned off(...) call throws, clears local wrapper state, stays idempotent, and does not leak the private teardown failure through React cleanup; and
  • WCAG relative-luminance math, provider listener scoping, package exports, SSR/browser behavior, and standalone no-service operation remain unchanged.

Test-first lineage

Earlier hosted lineage in this PR established RED→GREEN for display-name bounds, invalid remote identity counting, allocation-free Unicode bounds, invalid connection-status behavior, local collaboration-user runtime field validation, public contrast-color validation, the #309 remote source ceiling, descriptor-safe remote presentation, and the 1,024-code-unit remote cursor-name source preflight.

For provider-capability acquisition, test-only predecessor 7209a86d45908ab9bd9118591445ac6cf666cf49 introduced a provider whose first awareness read is valid and whose second read throws a private sentinel. Its hosted workflows were superseded/cancelled by the immediate source repair, so no hosted RED is claimed. Source repair eda95121085bfd42c14756002b7c531b88b008ae centralizes provider awareness acquisition and structural compatibility reads in readCompatibleCollaborationAwareness(), normalizing failures to the stable module-owned provider-capability error. Later regression de6f54b44fa9b7f771784c9f6635a451374b3029 covers a structural clientID getter failure.

The count-path defect was found on predecessor head 4b74b65cb52becc4e18f72ad480fe9d9c206ddfb: countRemoteCollaborators() directly read host-owned awareness.clientID and invoked awareness.getStates(), so either capability could throw a private value through the public helper. Test-only successor f07819b4252a0735cbe41619ef3c9117d5b7dc44 adds two realistic failure-containment regressions; source repair 6a01457ad7a044405a100a5b563f1b718fe3d220 wraps that host-owned count operation while retaining descriptor-safe remote identity rules.

The disposal defect was isolated on 6a01457ad7a044405a100a5b563f1b718fe3d220: dispose() called host-owned source.off(...) directly inside the nested cleanup loop, so one private teardown exception aborted all later detachments and escaped the component cleanup boundary despite the scoped-provider contract. Test-only RED d14dcd5aa935e21a3e7d10c12797f5ee8863b1b4 requires one throwing change detachment not to prevent update detachment, not to leak the sentinel, and to remain idempotent. The predecessor deterministically fails that contract; the immediate source repair superseded hosted RED generation, so no hosted RED is claimed. GREEN a03c7d04a11f7d7c7274e466247b38057fc19bb7 isolates each host detachment, continues cleanup, and clears local wrappers regardless of host failure.

The latest listener-operation defect remained after that cleanup repair: direct scoped on(...) inserted its wrapper before calling the host, so a rejected host registration both leaked the private host exception and poisoned the local map into a false-success retry; direct scoped off(...) also leaked a private host removal exception. Test-only commits be5e0a1a1d87a297160a259d3df24cca90c0ca47 and 3694bfaafc96501c0e164668a43870568ee74ce9 require stable payload-redacted registration/removal failures plus real retry semantics. Their workflow generations were superseded/cancelled by the immediate narrow source repair, so no hosted RED is claimed. Current GREEN d8407c5fc65027233c880cae2d1f2d14d5261d91 moves registration-state commit after successful host on(...), retains state after rejected host off(...), and normalizes both host failures to module-owned errors.

Exact-current-head evidence

For unchanged exact head d8407c5fc65027233c880cae2d1f2d14d5261d91 at the latest commit-bound refetch:

  • CI 32221759117: completed / success, including exact 100% root test coverage, package consumers, demo build, Office Python 3.11–3.14, and the Playwright 1.62.0 cross-engine clipboard job;
  • Security Scan 32221759122: completed / success;
  • SAST Semgrep 32221759109: completed / success;
  • formal submitted reviews: 0;
  • unresolved inline review threads: 0;
  • qualifying approving reviews: 0.

Repository exact-head technical success is not qualifying independent approval and does not replace separately applicable central workflows or live governance. Pending/queued/in-progress/skipped/cancelled/absent/stale/predecessor/status-only/model-only evidence remains non-passing.

Canonical consistency

docs/collaboration.md, protected PRD/TRD/contracts and architecture continue to state the intended authority accurately: remote awareness is ephemeral untrusted presentation data, not authorization evidence, and host/provider lifecycle authority remains outside Inkspan. These repairs narrow execution/resource/error-containment behavior within that existing boundary; they do not change provider, host, persistence, transport, model, or public semantic authority.

Draft #228 separately owns direct CollaborativeCwlEditor.tsx component interactions with host awareness. Its component-local getter/listener containment is complementary to this awareness-module lane and is not duplicated here.

Integration boundary

Release issue #118 owns exact protected v0.6.0 tag/publication/provenance from protected main. Keep this PR Draft and unmerged while that release boundary remains frozen and live governance is unsatisfied. After #118 permits integration work, refetch live main/head/ancestry/writers and reconcile this diverged branch non-destructively without reviving stale protected-control files or racing overlapping owners; then reacquire every exact-head/live-base repository/central workflow plus qualifying independent approval before any Ready/merge decision. Never transfer predecessor evidence, self-approve, weaken gates, or fabricate tag/release identity.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c22d2a91-bff3-41cf-9776-634a9dd62d41

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@seonghobae seonghobae changed the title fix(collaboration): bound outbound cursor labels fix(collaboration): harden awareness runtime contracts Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment