Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ This directory is the discoverable index for Inkspan's product, technical, secur
| [`TRD.md`](TRD.md) | Technical invariants, runtime boundaries, failure semantics and release evidence |
| [`CONTRACTS.md`](CONTRACTS.md) | Public package/API/event/schema/plugin/collaboration and host-integration contracts |
| [`package-distribution.md`](package-distribution.md) | Buyer-facing public npm package entrypoints, packaged contents, runtime dependency boundaries, and consumer verification |
| [`performance-envelope.md`](performance-envelope.md) | Proposed synthetic large-document browser baseline; no support budget is claimed before repeated measurements |
| [`email-output.md`](email-output.md) | Deterministic email fragment/full-document authority, language/direction metadata, accessibility and host-owned transport boundary |
| [`print-output.md`](print-output.md) | Browser print/paged-media presentation, accessibility/fidelity limits, host-owned governed-export boundary, and rollback |
| [`UML.md`](UML.md) | Component, sequence, state and authority-flow diagrams |
Expand Down
7 changes: 7 additions & 0 deletions docs/TEST_STRATEGY.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ Where browser fragment parsing or serialization participates in a security bound

The protected gate pins **Playwright 1.62.0** in an isolated immutable browser-test lock and runs the same versioned synthetic corpus through the supported TipTap/ProseMirror `transformPastedHTML` path in named Chromium, Firefox, and WebKit projects on one **exact source head**. Evidence binds the corpus version, browser-test lock digest, Playwright version, actual browser versions, operating-system identity, exact source head, fresh run identity, and exact packed npm artifact digest. The corpus covers active/resource/form content, hidden/Office/popover semantics, safe and unsafe links, malformed fragments, tables/lists, SVG/MathML, interactive/native fallback, byte/node/depth ceilings, hostile DOM capability failures, and a representative Word-like performance alarm.

Issue #375's proposed first slice separately runs the deterministic synthetic
`inkspan-large-document-v1` corpus through the real `CwlEditor` browser harness
for mount, snapshot, envelope serialization, and revision timing. This is
PR-CI smoke evidence only: it reports no document body or digest and claims no
support budget until repeated exact-head hardware/runtime measurements are
available.

Differences are not normalized away merely to produce parity. The default gate has no generic normalization or broad engine allowlist; a permitted difference requires a focused regression fixture, authoritative standards basis, threat analysis, exact affected engine/version evidence, canonical interpretation, compatibility impact, and rollback. Missing, skipped, cancelled, incomplete, stale-run, stale-lock, package-mismatched, or divergent required browser evidence must fail closed rather than becoming successful release evidence. A source movement after evidence generation requires the new exact release candidate to re-prove the gate.

### Office artifact tests
Expand Down
97 changes: 97 additions & 0 deletions docs/adr/0028-large-document-performance-baseline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# ADR 0028: Large-document performance baseline before support budgets

- Status: Proposed
- Date: 2026-08-21
- Owners: Inkspan editor/conversion maintainers
- Scope: Issue #375 PR-CI baseline only

## Context

Inkspan has correctness, package, browser, coverage, and deterministic
conversion checks, but protected `main` does not publish a measured large-
document latency or memory envelope. A favorable threshold chosen before a
reproducible baseline would be an unsupported buyer claim and could hide a
regression.

## Alternatives considered

1. Publish a document-size or 100 ms budget immediately. Rejected because no
repeated exact-head hardware/runtime baseline exists.
2. Add an unbounded benchmark that records complete document bodies or host
metadata. Rejected because benchmark evidence must remain synthetic and
privacy-minimized.
3. Add a bounded synthetic browser smoke and defer budgets until measurements
exist. Selected because it establishes the executable path with minimal new
authority.

## Decision

The initial performance lane uses a versioned synthetic multilingual corpus and
the packed-package browser harness to measure editor mount, detached snapshot,
canonical envelope serialization, and revision derivation in Chromium, Firefox,
and WebKit. It records only profile/count/length/timing metadata in test
attachments. The smoke requires finite non-negative measurements and successful
revision derivation but accepts no performance threshold.

Future budgets require repeated samples, named hardware and runtime profiles,
operation-specific p50/p75/p95/maximum values, peak-memory methodology where
trustworthy, and an explicit regression rule. They must be proposed in a later
ADR update rather than inferred from this smoke.

## Consequences

The repository gains a repeatable multilingual browser measurement path without
making a new runtime or package API. Buyers receive an executable baseline
signal, while a supported large-document envelope remains intentionally
unclaimed until stronger evidence exists.

## Failure and recovery

An unavailable benchmark operation must fail the active evidence run rather
than become an unmeasured success. A runner failure is an evidence failure, not
proof that Inkspan supports the profile. Maintainers rerun the same exact
source and corpus or record the environment defect before changing the
contract.

## Security and privacy impact

Inkspan owns deterministic local measurement of its editor/conversion calls.
Hosts own production telemetry, document classification, tenant policy,
authorization, retention, and any durable benchmark storage. The corpus and
attachments contain no customer content, credentials, prompts, comment bodies,
revision digests, or network authority. No benchmark code adds transport,
persistence, model, provider, or database authority.

## Compatibility and migration

The smoke uses existing public editor handles and does not change runtime
document semantics or package exports. A browser runner that cannot execute the
probe fails the active PR check; it is not treated as an unmeasured success. If
the corpus or measured operation changes, increment the corpus/contract record
and regenerate evidence on the exact source head. No document-schema or package
migration is introduced by this baseline.

## Verification

- `tests/browser/performanceCorpus.ts` is deterministic and multilingual.
- `tests/browser/harness.ts` measures the real `CwlEditor` and handle path.
- `tests/browser/specs/performance.browser.spec.ts` runs the profiles through
dependency-locked Chromium, Firefox, and WebKit with external requests
blocked.
- Existing TypeScript, Vitest, package, and release browser documentation
contracts remain required.

## Rollback or supersession

Remove or supersede this Proposed baseline if a later accepted performance
contract replaces it. Rolling back the smoke must not be described as evidence
of a supported performance envelope; the remaining correctness and release
gates continue to apply.

## Research basis

World Wide Web Consortium. (2026, March 19). *Event Timing API* (Working
Draft). https://www.w3.org/TR/event-timing/

Isik, P. (2025, June 24). *Faster, lighter, and more reliable DOCX
import/export with Tiptap*. Tiptap. https://tiptap.dev/blog/release-notes/faster-lighter-and-more-reliable-docx-import-export-with-tiptap
1 change: 1 addition & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ This index records durable architectural decisions. Protected-main implementatio
| [0024](0024-bounded-docx-paragraph-alignment.md) | Accepted | Bounded paragraph alignment in deterministic DOCX output |
| [0025](0025-bounded-docx-heading-alignment.md) | Accepted | Bounded heading alignment in deterministic DOCX output |
| [0026](0026-bounded-docx-external-hyperlinks.md) | Accepted | Bounded external hyperlinks in deterministic DOCX rich text |
| [0028](0028-large-document-performance-baseline.md) | Proposed | Synthetic large-document performance baseline before support budgets |

## Decision discipline

Expand Down
62 changes: 62 additions & 0 deletions docs/performance-envelope.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Large-document performance baseline

Status: Proposed active-PR baseline for Issue #375; not a protected-main support claim

Protected `main` remains the implementation authority. This first slice adds a
deterministic, synthetic browser smoke for editor mount, snapshot, canonical
envelope serialization, and SHA-256 revision derivation across Chromium,
Firefox, and WebKit. It deliberately does not publish a maximum document size,
latency budget, memory ceiling, device support statement, or Office conversion
budget before repeated exact-head measurements exist.

## Corpus and privacy boundary

`tests/browser/performanceCorpus.ts` defines the versioned
`inkspan-large-document-v1` corpus with English, Korean, Japanese, Simplified
and Traditional Chinese, Vietnamese, headings, and deterministic paragraph
profiles. It contains no customer content, tenant identifiers, prompts,
credentials, comment bodies, or network-loaded resources.

The browser probe reports only profile identity, paragraph count, source length,
snapshot length, revision availability, and bounded elapsed-time measurements.
It never records document text, serialized envelopes, revision digests, or host
identity. Playwright attachments are measurement artifacts for the active run,
not release or telemetry authority.

## Measured operations

For each profile and browser engine, the smoke records:

- React editor mount through `CwlEditor`;
- detached snapshot generation;
- canonical envelope JSON serialization; and
- SHA-256 revision derivation.

The current gate checks that measurements are finite, non-negative, revision
derivation succeeds, and snapshot output is non-empty. It does not compare
against an invented threshold. A later baseline PR must add repeated samples,
p50/p75/p95/maximum aggregation, trusted input and IME operations, autosave,
collaboration, print, and Office measurements before proposing support budgets.

## Evidence boundary and next step

This is PR-CI smoke evidence only. It is not the protected release browser
evidence contract, and it does not establish support for a browser, device,
Node runtime, Python runtime, document size, or memory envelope. The next
version must bind repeated measurements to exact hardware/runtime/browser
identity and immutable fixture hashes, then propose an accepted ADR and
regression rule from observed data.

Event Timing is used only as standards context for why input latency should be
measured; its current document is a W3C Working Draft, not an Inkspan
certification threshold (World Wide Web Consortium, 2026). Tiptap's public DOCX
performance report is market evidence that large-document conversion warrants
measurement, not an Inkspan performance claim (Isik, 2025).

## References

Isik, P. (2025, June 24). *Faster, lighter, and more reliable DOCX
import/export with Tiptap*. Tiptap. https://tiptap.dev/blog/release-notes/faster-lighter-and-more-reliable-docx-import-export-with-tiptap

World Wide Web Consortium. (2026, March 19). *Event Timing API* (Working
Draft). https://www.w3.org/TR/event-timing/
5 changes: 4 additions & 1 deletion scripts/revision-evidence-consumer-config.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,10 @@ test('copies the locked pnpm dependency tree into the independent consumer', ()
true,
);
const stagedPackage = realpathSync(join(targetNodeModules, 'example'));
assert.equal(relative(targetNodeModules, stagedPackage).startsWith('..'), false);
assert.equal(
relative(realpathSync(targetNodeModules), stagedPackage).startsWith('..'),
false,
);
} finally {
rmSync(root, { recursive: true, force: true });
}
Expand Down
5 changes: 4 additions & 1 deletion scripts/verify-framework-free-revision-evidence-package.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ function run(command, argumentsList, cwd = repositoryRoot) {

/** Assert that a resolved path remains inside the framework-free consumer. */
function assertInsideConsumer(resolvedPath, description) {
const relativePath = relative(consumerDirectory, realpathSync(resolvedPath));
const relativePath = relative(
realpathSync(consumerDirectory),
realpathSync(resolvedPath),
);
assert.equal(isAbsolute(relativePath), false, description);
assert.equal(
relativePath === '..' || relativePath.startsWith(`..${sep}`),
Expand Down
11 changes: 7 additions & 4 deletions scripts/verify-revision-evidence-package.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,10 @@ function readInstalledDependencyVersion(packageName) {

/** Assert that a resolved path cannot escape the independent consumer tree. */
function assertPathInsideConsumer(resolvedPath, description) {
const relativePath = relative(verificationDirectory, resolvedPath);
const relativePath = relative(
realpathSync(verificationDirectory),
realpathSync(resolvedPath),
);
assert.equal(
isAbsolute(relativePath),
false,
Expand Down Expand Up @@ -258,7 +261,7 @@ import { isAbsolute, relative, sep } from 'node:path';
import { fileURLToPath } from 'node:url';
import * as editor from '${packageJson.name}';

const consumerDirectory = ${JSON.stringify(verificationDirectory)};
const consumerDirectory = ${JSON.stringify(realpathSync(verificationDirectory))};
function assertInsideConsumer(resolvedPath, description) {
const resolvedRelative = relative(consumerDirectory, resolvedPath);
assert.equal(isAbsolute(resolvedRelative), false, description);
Expand All @@ -270,7 +273,7 @@ function assertInsideConsumer(resolvedPath, description) {
}
const resolvedEntry = fileURLToPath(import.meta.resolve('${packageJson.name}'));
assertInsideConsumer(resolvedEntry, 'packed ESM entry escaped consumer tree');
const packageRelative = relative(${JSON.stringify(packageDirectory)}, resolvedEntry);
const packageRelative = relative(${JSON.stringify(realpathSync(packageDirectory))}, resolvedEntry);
assert.equal(isAbsolute(packageRelative), false);
assert.equal(
packageRelative === '..' || packageRelative.startsWith('..' + sep),
Expand Down Expand Up @@ -329,7 +332,7 @@ const editor = require('${packageJson.name}');

void (async () => {
const resolvedEntry = require.resolve('${packageJson.name}');
const resolvedRelative = relative(${JSON.stringify(packageDirectory)}, resolvedEntry);
const resolvedRelative = relative(${JSON.stringify(realpathSync(packageDirectory))}, resolvedEntry);
assert.equal(isAbsolute(resolvedRelative), false);
assert.equal(
resolvedRelative === '..' || resolvedRelative.startsWith('..' + sep),
Expand Down
37 changes: 37 additions & 0 deletions src/performanceBenchmarkDocumentation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { describe, expect, it } from 'vitest';

function repositoryFile(path: string): string {
return readFileSync(resolve(process.cwd(), path), 'utf8');
}

describe('large-document performance baseline documentation', () => {
it('keeps the proposed baseline distinct from a support claim', () => {
const envelope = repositoryFile('docs/performance-envelope.md');
const adr = repositoryFile(
'docs/adr/0028-large-document-performance-baseline.md',
);

expect(envelope).toContain('Status: Proposed active-PR baseline');
expect(envelope).toContain('inkspan-large-document-v1');
expect(envelope).toContain('does not publish a maximum document size');
expect(adr).toContain('- Status: Proposed');
expect(adr).toContain('No benchmark code adds transport');
});

it('keeps the browser gate and smoke spec discoverable', () => {
const config = repositoryFile('tests/browser/playwright.config.ts');
const spec = repositoryFile(
'tests/browser/specs/performance.browser.spec.ts',
);
const corpus = repositoryFile('tests/browser/performanceCorpus.ts');
const harness = repositoryFile('tests/browser/harness.ts');

expect(config).toContain('performance');
expect(spec).toContain('PERFORMANCE_CORPUS_VERSION');
expect(spec).toContain('without body telemetry');
expect(corpus).toContain("'inkspan-large-document-v1'");
expect(harness).toContain('runInkspanDocumentPerformanceProbe');
});
});
Loading
Loading