From 94261deacae25983b76a409c990e2a8a8a0cb692 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 03:15:33 +0900 Subject: [PATCH 01/11] test(reliability): expose unbounded collaboration field metadata --- ...laborativeCwlEditor.fieldBoundary.test.tsx | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/collaboration/CollaborativeCwlEditor.fieldBoundary.test.tsx diff --git a/src/collaboration/CollaborativeCwlEditor.fieldBoundary.test.tsx b/src/collaboration/CollaborativeCwlEditor.fieldBoundary.test.tsx new file mode 100644 index 00000000..c25b27de --- /dev/null +++ b/src/collaboration/CollaborativeCwlEditor.fieldBoundary.test.tsx @@ -0,0 +1,58 @@ +import { cleanup, render, screen } from '@testing-library/react'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import * as Y from 'yjs'; +import { CollaborativeCwlEditor } from './CollaborativeCwlEditor.js'; +import type { CollaborativeCwlEditorProps } from './types.js'; + +const COLLABORATION_FIELD_MAX_CODE_UNITS = 1_024; +const INVALID_COLLABORATION_FIELD_MESSAGE = + 'Collaboration field must be a string within the supported length.'; + +afterEach(cleanup); + +function captureRenderFailure(field: unknown): unknown { + const consoleError = vi + .spyOn(console, 'error') + .mockImplementation(() => undefined); + let failure: unknown; + try { + render( + , + ); + } catch (error) { + failure = error; + } finally { + consoleError.mockRestore(); + } + return failure; +} + +describe('CollaborativeCwlEditor field resource boundary', () => { + it('rejects non-string runtime field metadata through a stable redacted error', () => { + expect(captureRenderFailure(42)).toEqual( + new RangeError(INVALID_COLLABORATION_FIELD_MESSAGE), + ); + }); + + it('rejects oversized field metadata before normalization without reflecting it', () => { + const privateMarker = 'private-room-marker'; + const field = `${privateMarker}${'x'.repeat(COLLABORATION_FIELD_MAX_CODE_UNITS)}`; + const failure = captureRenderFailure(field); + + expect(failure).toEqual(new RangeError(INVALID_COLLABORATION_FIELD_MESSAGE)); + expect(String(failure)).not.toContain(privateMarker); + }); + + it('accepts an in-bound custom field at the local ceiling', () => { + const field = 'x'.repeat(COLLABORATION_FIELD_MAX_CODE_UNITS); + + render(); + + expect(screen.getByRole('status')).toHaveTextContent( + 'Collaboration ready ยท 0 remote collaborators', + ); + }); +}); From 0a5f8c34807de11635001cf952aa718818da6aef Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 03:20:05 +0900 Subject: [PATCH 02/11] fix(reliability): bound collaboration field metadata --- src/collaboration/CollaborativeCwlEditor.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/collaboration/CollaborativeCwlEditor.tsx b/src/collaboration/CollaborativeCwlEditor.tsx index eea89b6c..a233cc69 100644 --- a/src/collaboration/CollaborativeCwlEditor.tsx +++ b/src/collaboration/CollaborativeCwlEditor.tsx @@ -33,6 +33,10 @@ import { } from './awareness.js'; import type { CollaborativeCwlEditorProps } from './types.js'; +const COLLABORATION_FIELD_MAX_CODE_UNITS = 1_024; +const INVALID_COLLABORATION_FIELD_MESSAGE = + 'Collaboration field must be a string within the supported length.'; + /** * Provider-neutral collaborative Inkspan surface backed exclusively by a * host-owned Yjs document. Inkspan owns neither network nor persistence @@ -98,7 +102,14 @@ export const CollaborativeCwlEditor = forwardRef< } = props; assertCollaborationConfiguration(provider, user); - if (field.trim() === '') { + if ( + typeof field !== 'string' || + field.length > COLLABORATION_FIELD_MAX_CODE_UNITS + ) { + throw new RangeError(INVALID_COLLABORATION_FIELD_MESSAGE); + } + const normalizedField = field.trim(); + if (normalizedField === '') { throw new Error('collaboration field must not be empty'); } if ( @@ -108,7 +119,6 @@ export const CollaborativeCwlEditor = forwardRef< throw new Error('collaboration document must be a Y.Doc instance'); } - const normalizedField = field.trim(); const normalizedPlaceholder = useMemo( () => normalizeEditorPlaceholder(placeholder), [placeholder], From cf39f9e5b5c2e1b0c6b9c91d49b5d841ccdff674 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 18:31:28 +0900 Subject: [PATCH 03/11] test(data-integrity): expose collaborative runtime state coercion --- ...llaborativeCwlEditor.runtimeState.test.tsx | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 src/collaboration/CollaborativeCwlEditor.runtimeState.test.tsx diff --git a/src/collaboration/CollaborativeCwlEditor.runtimeState.test.tsx b/src/collaboration/CollaborativeCwlEditor.runtimeState.test.tsx new file mode 100644 index 00000000..72607f78 --- /dev/null +++ b/src/collaboration/CollaborativeCwlEditor.runtimeState.test.tsx @@ -0,0 +1,60 @@ +// @vitest-environment node + +import { renderToString } from 'react-dom/server'; +import { describe, expect, it } from 'vitest'; +import * as Y from 'yjs'; +import { CollaborativeCwlEditor } from './CollaborativeCwlEditor.js'; + +describe('collaborative editor runtime state contracts', () => { + it('rejects a non-boolean editable state instead of coercing it into edit authority', () => { + expect(() => + renderToString( + , + ), + ).toThrowError( + new RangeError('editor editable state must be a boolean when provided'), + ); + }); + + it('rejects a non-boolean toolbar visibility state instead of coercing it', () => { + expect(() => + renderToString( + , + ), + ).toThrowError( + new RangeError( + 'editor toolbar visibility state must be a boolean when provided', + ), + ); + }); + + it('preserves omitted and explicit boolean states', () => { + expect(() => + renderToString(), + ).not.toThrow(); + expect(() => + renderToString( + , + ), + ).not.toThrow(); + expect(() => + renderToString( + , + ), + ).not.toThrow(); + }); +}); From 87ee3e6cebe42714912f2350e34bbe5028a0e4b3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 18:35:00 +0900 Subject: [PATCH 04/11] fix(data-integrity): validate collaborative runtime state --- src/collaboration/CollaborativeCwlEditor.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/collaboration/CollaborativeCwlEditor.tsx b/src/collaboration/CollaborativeCwlEditor.tsx index a233cc69..b4ac3078 100644 --- a/src/collaboration/CollaborativeCwlEditor.tsx +++ b/src/collaboration/CollaborativeCwlEditor.tsx @@ -101,6 +101,14 @@ export const CollaborativeCwlEditor = forwardRef< ariaRequired, } = props; + if (typeof editable !== 'boolean') { + throw new RangeError('editor editable state must be a boolean when provided'); + } + if (typeof hideToolbar !== 'boolean') { + throw new RangeError( + 'editor toolbar visibility state must be a boolean when provided', + ); + } assertCollaborationConfiguration(provider, user); if ( typeof field !== 'string' || From 0970e5cd70dd15ed8a9fde5e03cdf70ea75d3579 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 22:34:18 -0700 Subject: [PATCH 05/11] test(collaboration): prove provider awareness read containment --- ...wlEditor.providerAwarenessFailure.test.tsx | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 src/collaboration/CollaborativeCwlEditor.providerAwarenessFailure.test.tsx diff --git a/src/collaboration/CollaborativeCwlEditor.providerAwarenessFailure.test.tsx b/src/collaboration/CollaborativeCwlEditor.providerAwarenessFailure.test.tsx new file mode 100644 index 00000000..90720322 --- /dev/null +++ b/src/collaboration/CollaborativeCwlEditor.providerAwarenessFailure.test.tsx @@ -0,0 +1,51 @@ +// @vitest-environment node + +import { renderToString } from 'react-dom/server'; +import { describe, expect, it } from 'vitest'; +import * as Y from 'yjs'; +import { CollaborativeCwlEditor } from './CollaborativeCwlEditor.js'; +import type { + CollaborationAwareness, + CollaborationProviderLike, +} from './types.js'; + +function validAwareness(): CollaborationAwareness { + const states = new Map>(); + return { + clientID: 17, + states, + getLocalState: () => null, + getStates: () => states, + setLocalStateField: () => undefined, + on: () => undefined, + off: () => undefined, + }; +} + +describe('collaborative editor provider awareness access', () => { + it('contains a private awareness getter failure after configuration validation', () => { + const privateFailure = new Error('sensitive-provider-awareness-internal'); + const awareness = validAwareness(); + let reads = 0; + const provider = Object.defineProperty({}, 'awareness', { + enumerable: true, + get() { + reads += 1; + if (reads === 1) return awareness; + throw privateFailure; + }, + }) as CollaborationProviderLike; + + let observed: unknown; + try { + renderToString( + , + ); + } catch (error) { + observed = error; + } + + expect(observed).toBeUndefined(); + expect(reads).toBe(2); + }); +}); From fbe4d3ae34e1c5e0d080ef9076a4f0ebd539faa7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 22:34:56 -0700 Subject: [PATCH 06/11] fix(collaboration): contain provider awareness getter failures --- src/collaboration/CollaborativeCwlEditor.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/collaboration/CollaborativeCwlEditor.tsx b/src/collaboration/CollaborativeCwlEditor.tsx index b4ac3078..645cad4f 100644 --- a/src/collaboration/CollaborativeCwlEditor.tsx +++ b/src/collaboration/CollaborativeCwlEditor.tsx @@ -37,6 +37,17 @@ const COLLABORATION_FIELD_MAX_CODE_UNITS = 1_024; const INVALID_COLLABORATION_FIELD_MESSAGE = 'Collaboration field must be a string within the supported length.'; +/** Read host-owned awareness for presentation without leaking getter failures. */ +function readProviderAwareness( + provider: CollaborativeCwlEditorProps['provider'], +) { + try { + return provider?.awareness; + } catch { + return undefined; + } +} + /** * Provider-neutral collaborative Inkspan surface backed exclusively by a * host-owned Yjs document. Inkspan owns neither network nor persistence @@ -307,10 +318,10 @@ export const CollaborativeCwlEditor = forwardRef< ]); const [remoteCollaborators, setRemoteCollaborators] = useState(() => - countRemoteCollaborators(provider?.awareness), + countRemoteCollaborators(readProviderAwareness(provider)), ); useEffect(() => { - const awareness = provider?.awareness; + const awareness = readProviderAwareness(provider); const updateCount = () => { setRemoteCollaborators(countRemoteCollaborators(awareness)); }; From 5b7a67e64cd837008d85a6375685c239205c0dca Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 22:35:50 -0700 Subject: [PATCH 07/11] test(collaboration): prove listener failure containment --- ...CwlEditor.providerListenerFailure.test.tsx | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 src/collaboration/CollaborativeCwlEditor.providerListenerFailure.test.tsx diff --git a/src/collaboration/CollaborativeCwlEditor.providerListenerFailure.test.tsx b/src/collaboration/CollaborativeCwlEditor.providerListenerFailure.test.tsx new file mode 100644 index 00000000..42861b59 --- /dev/null +++ b/src/collaboration/CollaborativeCwlEditor.providerListenerFailure.test.tsx @@ -0,0 +1,87 @@ +import { cleanup, render } from '@testing-library/react'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import * as Y from 'yjs'; +import { CollaborativeCwlEditor } from './CollaborativeCwlEditor.js'; +import type { + CollaborationAwareness, + CollaborationAwarenessEvent, + CollaborationProviderLike, +} from './types.js'; + +afterEach(cleanup); + +function awarenessWith( + on: CollaborationAwareness['on'], + off: CollaborationAwareness['off'], +): CollaborationAwareness { + const states = new Map>(); + return { + clientID: 23, + states, + getLocalState: () => null, + getStates: () => states, + setLocalStateField: () => undefined, + on, + off, + }; +} + +describe('collaborative editor provider listener failure containment', () => { + it('does not leak a private change-listener registration failure', () => { + const privateFailure = new Error('sensitive-provider-on-internal'); + const awareness = awarenessWith( + (_event: CollaborationAwarenessEvent) => { + throw privateFailure; + }, + () => undefined, + ); + const provider: CollaborationProviderLike = { awareness }; + const consoleError = vi + .spyOn(console, 'error') + .mockImplementation(() => undefined); + + let observed: unknown; + try { + render( + , + ); + } catch (error) { + observed = error; + } finally { + consoleError.mockRestore(); + } + + expect(observed).toBeUndefined(); + }); + + it('does not leak a private change-listener cleanup failure', () => { + const privateFailure = new Error('sensitive-provider-off-internal'); + let offCalls = 0; + const awareness = awarenessWith( + () => undefined, + () => { + offCalls += 1; + throw privateFailure; + }, + ); + const provider: CollaborationProviderLike = { awareness }; + const mounted = render( + , + ); + const consoleError = vi + .spyOn(console, 'error') + .mockImplementation(() => undefined); + + let observed: unknown; + try { + mounted.unmount(); + } catch (error) { + observed = error; + } finally { + consoleError.mockRestore(); + } + + expect(observed).toBeUndefined(); + expect(offCalls).toBeGreaterThan(0); + }); +}); From 8834f15bfc5006c041afeea2fb696498ffdd1106 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 22:36:23 -0700 Subject: [PATCH 08/11] fix(collaboration): contain provider listener failures --- src/collaboration/CollaborativeCwlEditor.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/collaboration/CollaborativeCwlEditor.tsx b/src/collaboration/CollaborativeCwlEditor.tsx index 645cad4f..a0f1166e 100644 --- a/src/collaboration/CollaborativeCwlEditor.tsx +++ b/src/collaboration/CollaborativeCwlEditor.tsx @@ -327,8 +327,18 @@ export const CollaborativeCwlEditor = forwardRef< }; updateCount(); if (!awareness) return; - awareness.on('change', updateCount); - return () => awareness.off('change', updateCount); + try { + awareness.on('change', updateCount); + } catch { + return; + } + return () => { + try { + awareness.off('change', updateCount); + } catch { + // Host-owned listener cleanup failure is contained at unmount. + } + }; }, [provider]); const collaboratorLabel = From ceb8f40172027e996ccfb83c450545f12a437a74 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 02:26:47 +0900 Subject: [PATCH 09/11] test(ci): cover event-specific Python matrix Signed-off-by: Seongho Bae --- office/tests/test_python_support_contract.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/office/tests/test_python_support_contract.py b/office/tests/test_python_support_contract.py index 7104fd66..209f4845 100644 --- a/office/tests/test_python_support_contract.py +++ b/office/tests/test_python_support_contract.py @@ -50,10 +50,14 @@ def test_python_support_range_matches_classifiers_and_ci_matrix() -> None: office_job = _workflow_job_block(workflow, "office") assert "runs-on: ubuntu-24.04" in office_job assert "runs-on: ubuntu-latest" not in office_job - matrix_match = re.search(r'python-version:\s*\[([^\]]+)\]', office_job) + matrix_match = re.search(r"python-version:\s*(.+)", office_job) assert matrix_match is not None - matrix_versions = tuple(re.findall(r'"(3\.\d+)"', matrix_match.group(1))) - assert matrix_versions == SUPPORTED_PYTHON_VERSIONS + pull_request_versions, push_versions = ( + tuple(re.findall(r'"(3\.\d+)"', versions)) + for versions in re.findall(r"fromJSON\('(\[[^']+\])'\)", matrix_match.group(1)) + ) + assert pull_request_versions == (SUPPORTED_PYTHON_VERSIONS[-1],) + assert push_versions == SUPPORTED_PYTHON_VERSIONS def test_python_support_documentation_matches_the_fixed_ci_environment() -> None: From 1b73980e645f0cdea7abd098a7646cde90d36b5e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 02:32:54 +0900 Subject: [PATCH 10/11] test(ci): bind Python matrix to event Signed-off-by: Seongho Bae --- office/tests/test_python_support_contract.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/office/tests/test_python_support_contract.py b/office/tests/test_python_support_contract.py index 209f4845..a52ddec3 100644 --- a/office/tests/test_python_support_contract.py +++ b/office/tests/test_python_support_contract.py @@ -50,11 +50,16 @@ def test_python_support_range_matches_classifiers_and_ci_matrix() -> None: office_job = _workflow_job_block(workflow, "office") assert "runs-on: ubuntu-24.04" in office_job assert "runs-on: ubuntu-latest" not in office_job - matrix_match = re.search(r"python-version:\s*(.+)", office_job) + matrix_match = re.search( + r"python-version:\s*\$\{\{\s*github\.event_name\s*==\s*'pull_request'" + r"\s*&&\s*fromJSON\('(\[[^']+\])'\)\s*\|\|\s*" + r"fromJSON\('(\[[^']+\])'\)\s*\}\}", + office_job, + ) assert matrix_match is not None pull_request_versions, push_versions = ( tuple(re.findall(r'"(3\.\d+)"', versions)) - for versions in re.findall(r"fromJSON\('(\[[^']+\])'\)", matrix_match.group(1)) + for versions in matrix_match.groups() ) assert pull_request_versions == (SUPPORTED_PYTHON_VERSIONS[-1],) assert push_versions == SUPPORTED_PYTHON_VERSIONS From e9521459e0d2bf46b90006d71505132dcb977a52 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 06:27:22 +0900 Subject: [PATCH 11/11] revert(ci): restore Office contract owner Remove the duplicated Python support contract changes from this collaboration branch. PR #405 remains the single writer while this branch keeps its runtime field boundary delta. Signed-off-by: Seongho Bae Commit-Message-Assisted-by: Claude (via Claude Code) --- office/tests/test_python_support_contract.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/office/tests/test_python_support_contract.py b/office/tests/test_python_support_contract.py index a52ddec3..7104fd66 100644 --- a/office/tests/test_python_support_contract.py +++ b/office/tests/test_python_support_contract.py @@ -50,19 +50,10 @@ def test_python_support_range_matches_classifiers_and_ci_matrix() -> None: office_job = _workflow_job_block(workflow, "office") assert "runs-on: ubuntu-24.04" in office_job assert "runs-on: ubuntu-latest" not in office_job - matrix_match = re.search( - r"python-version:\s*\$\{\{\s*github\.event_name\s*==\s*'pull_request'" - r"\s*&&\s*fromJSON\('(\[[^']+\])'\)\s*\|\|\s*" - r"fromJSON\('(\[[^']+\])'\)\s*\}\}", - office_job, - ) + matrix_match = re.search(r'python-version:\s*\[([^\]]+)\]', office_job) assert matrix_match is not None - pull_request_versions, push_versions = ( - tuple(re.findall(r'"(3\.\d+)"', versions)) - for versions in matrix_match.groups() - ) - assert pull_request_versions == (SUPPORTED_PYTHON_VERSIONS[-1],) - assert push_versions == SUPPORTED_PYTHON_VERSIONS + matrix_versions = tuple(re.findall(r'"(3\.\d+)"', matrix_match.group(1))) + assert matrix_versions == SUPPORTED_PYTHON_VERSIONS def test_python_support_documentation_matches_the_fixed_ci_environment() -> None: