From e00437ebc5e7778c637e34a44bab46d3d88ee5c4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 09:08:52 +0900 Subject: [PATCH 01/18] docs(adr): define host-owned LLM writing diagnostics boundary --- ...0027-host-owned-llm-writing-diagnostics.md | 152 ++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 docs/adr/0027-host-owned-llm-writing-diagnostics.md diff --git a/docs/adr/0027-host-owned-llm-writing-diagnostics.md b/docs/adr/0027-host-owned-llm-writing-diagnostics.md new file mode 100644 index 00000000..05d9e0f4 --- /dev/null +++ b/docs/adr/0027-host-owned-llm-writing-diagnostics.md @@ -0,0 +1,152 @@ +# ADR 0027: Host-owned, revision-bound LLM writing diagnostics + +Status: Proposed + +## Context + +Inkspan already provides deterministic authoring, revision evidence, revision-scoped W3C `TextPositionSelector` evidence, guarded restore, and a host-owned model-assistance boundary. Host applications now need a Grammarly-like writing-guidance surface that can underline a passage, explain a problem, propose a replacement, let the author apply or ignore it, and keep every suggestion bound to the exact document revision from which it was produced. + +The requested quality judgments include spelling, grammar, clarity, concision, discourse structure, workplace pragmatics, audience appropriateness, technical precision, actionability, and preservation of the author's intended request. These are contextual language judgments. Fixed keyword lists, regular-expression phrase detectors, domain-suffix lists, hand-written “aggressive phrase” tables, and positional repair rules cannot establish those meanings and create brittle false positives and false negatives across paraphrases, quotations, languages, and recipient contexts. + +Inkspan must remain a modular editor. It must not gain email semantics, tenant policy, model credentials, network transport, an LLM provider dependency, or authority to decide whether a sentence is appropriate. At the same time, hosts should not have to scrape ProseMirror DOM nodes or maintain a private editor fork to display revision-safe diagnostics. + +## Alternatives considered + +- **Host-specific DOM overlays over Inkspan.** Rejected because DOM offsets are not a supported document contract, become stale after ProseMirror transactions, fragment keyboard and screen-reader behavior, and force every host to rebuild decorations, navigation, application, conflict handling, and undo semantics. +- **A deterministic keyword or regex checker inside Inkspan.** Rejected because lexical triggers are not evidence of grammar, intent, tone, pragmatics, or technical correctness. Deterministic code may validate data shape and document coordinates, but it may not manufacture semantic judgments. +- **Inkspan invokes an LLM directly.** Rejected because the editor would acquire provider, credential, network, privacy, retention, availability, model-routing, and tenant-policy responsibilities that belong to the host. +- **The host sends whole-document rewrites and calls `setValue`.** Rejected because whole-document replacement obscures individual reasons, weakens author control, destroys revision-local review evidence, and makes accidental intent changes harder to detect. +- **Host-owned LLM judgments rendered through a generic Inkspan diagnostic contract.** Selected because it preserves Inkspan's provider-neutral deterministic core while giving every host one revision-safe, accessible writing-assistance surface. + +## Decision + +Inkspan will expose a generic, additive writing-diagnostic presentation and application contract. The host supplies already-produced diagnostic proposals. Inkspan validates, anchors, renders, navigates, applies, ignores, and reports actions on those proposals, but does not decide whether the prose is correct or appropriate. + +A diagnostic must include, at minimum: + +- a bounded opaque `diagnostic_id`; +- the exact Inkspan strong document revision used by the host's review operation; +- the declared text-projection identity and version; +- a revision-scoped W3C `TextPositionSelector` with inclusive `start` and exclusive `end` Unicode-code-point offsets; +- a bounded host-defined `category_code` and display-safe title; +- an explanation; +- an optional proposed replacement; +- an optional bounded confidence value; +- provider/workflow provenance identifiers that contain no source text or credential; +- a host policy or judge-policy version identifying how the proposal was admitted. + +Inkspan may perform only deterministic validation and document operations: + +- schema, type, enum, length, count, duplicate-id, and resource-bound validation; +- projection-version and strong-revision equality checks; +- Unicode-code-point and grapheme-boundary checks; +- selector range checks against the exact projected text; +- safe text/markup handling under the existing editor security policy; +- decoration mapping through local ProseMirror transactions when that mapping remains valid; +- stale-result invalidation when the declared revision or projection no longer matches; +- overlap/conflict detection between proposed replacements; +- one-action-at-a-time replacement, ordinary editor undo, and action callbacks. + +Inkspan must not infer a diagnostic from keywords, regexes, phrase lists, sender domains, recipient counts, language names, or word position. Such mechanisms may validate identifiers or transport contracts, but they may not be used as a semantic fallback. If the host's model path is unavailable or returns no admitted diagnostics, Inkspan displays no fabricated judgment. + +Applying a diagnostic is an explicit author action. A replacement is applied only if the current document still matches the diagnostic's expected revision or if Inkspan can prove a valid transaction-local mapping under the published lifecycle contract. A stale or ambiguous diagnostic never mutates the document. It returns a typed conflict or invalidation result so the host can request a fresh review. + +Diagnostics are advisory. Inkspan does not block form submission, email sending, persistence, or export merely because diagnostics remain. A host may implement a separate product policy, but that policy is outside the editor package and cannot be inferred from Inkspan diagnostic priority or confidence. + +## Ownership boundary + +Inkspan owns: + +- document state and serialization; +- revision and selector evidence; +- diagnostic schema validation; +- decorations and accessible suggestion navigation; +- explicit apply/ignore/dismiss actions; +- stale-result and overlap conflict handling; +- ordinary document undo and focus restoration; +- privacy-minimized action callbacks. + +The host owns: + +- model selection and orchestration; +- prompts, rubrics, examples, and language policy; +- source email, thread, recipient, role, and organization context; +- semantic categories, confidence calibration, acceptance policy, and abstention; +- provider credentials and data-processing approval; +- diagnostic persistence and retention; +- feedback collection, evaluation, monitoring, and human escalation; +- any send, save, or compliance gate. + +## Consequences + +A host can provide inline writing guidance without forking the editor. Inkspan remains usable offline and when every model provider is unavailable. The public contract becomes broader and therefore requires packed-package, standalone, collaborative, SSR, accessibility, and cross-engine evidence. Hosts must operate a real review service and cannot treat the editor as an evaluator. + +The decision intentionally separates semantic authority from deterministic integrity. A model or calibrated judge may be wrong about the prose; Inkspan can still guarantee that the proposal was not silently moved to an unrelated span or applied to a different revision. + +## Failure and recovery + +- Missing, malformed, oversized, duplicate, unsupported-projection, or out-of-range diagnostics are rejected without document mutation. +- A provider timeout, quota error, malformed model result, or host policy abstention is represented by absence or a host-owned status outside the diagnostic list. Inkspan authoring remains available. +- A changed document invalidates stale diagnostics. Inkspan never “repairs” them by searching for keywords or selecting the nearest matching sentence. +- A safely mapped local transaction may preserve a diagnostic only when the mapping contract proves the selected range still denotes the intended content. Ambiguity invalidates it. +- Overlapping replacements are applied separately and revalidated after every mutation. “Apply all” is permitted only for an explicitly validated non-overlapping batch under one current revision. +- A collaborative remote edit follows the same invalidation rule; raw local positions are not durable Yjs anchors. + +## Security and privacy impact + +Diagnostics and replacements are untrusted host-controlled input. They pass through the same safe-link, inline-image, clipboard, schema, and transaction boundaries as other editor input. A diagnostic must not contain model credentials, raw provider request/response bodies, tenant identifiers not needed by the component, or hidden executable markup. + +The public diagnostic contract does not require copying the selected source text. Position selectors remain revision-scoped and privacy-minimized. Hosts that add quote selectors, store explanations, or transmit the document to a model own authorization, encryption, provider data-use terms, retention, audit, and regional-processing controls. + +Generic telemetry may record bounded category, action, conflict reason, latency bucket, and policy version. It must not record authored source text, suggested replacement text, full explanations, prompts, raw model output, or document envelopes by default. + +## Accessibility + +Diagnostics must be available without relying on color or hover. The eventual implementation must provide: + +- a keyboard-reachable diagnostics summary; +- previous/next diagnostic navigation; +- an accessible name for category and affected passage; +- predictable focus movement between editor range and suggestion card; +- explicit Apply, Ignore, Dismiss, and Explain actions; +- polite status announcements after application or invalidation; +- no focus theft while new asynchronous diagnostics arrive; +- equivalent behavior in standalone and collaborative editors. + +Underlines are a visual supplement, not the sole information channel. + +## Compatibility and migration + +The feature is additive. Existing hosts that do not pass diagnostics retain identical editor, serialization, form, persistence, and collaboration behavior. The diagnostic contract must remain optional and provider-neutral. It may be released only in a version whose package declarations, ESM/CJS outputs, React peer ranges, styles, and consumer verification expose the same contract. + +No canonical document-envelope schema change is required. Diagnostics are review state, not canonical document content. Rollback removes the diagnostic props, decorations, and action surface without document migration. + +## Verification + +Acceptance requires tests proving: + +- strict resource-bounded schema validation and duplicate rejection; +- Unicode astral characters, Korean/CJK text, combining marks, emoji, bidirectional text, and grapheme-boundary behavior; +- exact revision/projection binding; +- stale-result rejection without mutation; +- transaction mapping only when meaning-preserving range identity is provable; +- overlapping replacement conflict behavior; +- single and bounded batch application plus undo; +- safe rendering of hostile titles, explanations, and replacements; +- keyboard, focus, live-region, and screen-reader semantics; +- standalone/collaborative parity and remote-edit invalidation; +- SSR-safe initial shell and hydration; +- packed ESM/CommonJS/types/CSS consumer compatibility; +- no model SDK, credential, environment, database, or network dependency in the Inkspan package; +- no source text or replacement text in default telemetry; +- exact 100% production statement, branch, function, and line coverage and complete public API documentation. + +Contract tests must also prohibit semantic keyword fallback: adversarial fixtures with identical keywords but different meanings, and paraphrases with different words but the same issue, must prove that Inkspan itself produces no judgment. It renders only host-supplied diagnostics. + +## Research and standards traceability + +This decision uses the W3C Web Annotation Data Model's Unicode-code-point `TextPositionSelector` semantics together with Inkspan's stronger revision binding. It treats LLM judgments as fallible measurement outputs rather than deterministic truth, consistent with published findings on position, verbosity, self-preference, artifact, multilingual, and consistency biases in LLM evaluators. The accompanying design and doctoring records contain APA 7th citations and the host-side calibration implications. + +## Rollback or supersession + +Rollback removes the optional diagnostic surface while preserving canonical documents, revision evidence, selection evidence, and deterministic authoring. Supersession requires a new ADR if Inkspan is ever proposed to own model invocation, semantic classification, persistence authority, or submission policy. Such a change must provide explicit provider neutrality, privacy, offline/degraded operation, accessibility, compatibility, migration, and rollback evidence. \ No newline at end of file From e96f8712e301e56e4dc0926bde87894adcb92426 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 09:09:53 +0900 Subject: [PATCH 02/18] docs(spec): design revision-bound writing diagnostics --- ...on-bound-llm-writing-diagnostics-design.md | 340 ++++++++++++++++++ 1 file changed, 340 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-12-revision-bound-llm-writing-diagnostics-design.md diff --git a/docs/superpowers/specs/2026-08-12-revision-bound-llm-writing-diagnostics-design.md b/docs/superpowers/specs/2026-08-12-revision-bound-llm-writing-diagnostics-design.md new file mode 100644 index 00000000..8e753eb2 --- /dev/null +++ b/docs/superpowers/specs/2026-08-12-revision-bound-llm-writing-diagnostics-design.md @@ -0,0 +1,340 @@ +# Revision-Bound LLM Writing Diagnostics Design + +**Date:** 2026-08-12 +**Status:** Proposed design; not shipped behavior +**Target:** Next Inkspan feature release after the current protected-main release train + +## Objective + +Add a provider-neutral, Grammarly-like writing-diagnostic surface to Inkspan without turning Inkspan into a language model, email product, policy engine, or persistence service. + +A host application will generate contextual writing proposals using an LLM and its own review policy. Inkspan will display those proposals against the exact document revision from which they were generated, let the author inspect and apply or ignore each one, and prevent stale asynchronous output from mutating newer content. + +The feature must support spelling, grammar, spacing, punctuation, clarity, concision, structure, tone, pragmatics, technical precision, and actionability as host-defined categories. Inkspan does not determine any of those categories. It exposes a generic review contract and deterministic document integrity. + +## Product behavior + +An author sees normal Inkspan editing first. When the host supplies diagnostics: + +1. affected ranges receive non-color-only decorations; +2. the diagnostics summary reports the number and categories of suggestions; +3. keyboard and pointer users can move to the previous or next suggestion; +4. a suggestion card explains the issue and shows an optional replacement; +5. Apply changes only the selected range; +6. Ignore reports a host-visible feedback action without changing the document; +7. Dismiss removes the local presentation until the host changes the diagnostic set; +8. Explain requests no model call from Inkspan; it reveals the explanation already supplied by the host or invokes a host callback; +9. any document change revalidates or invalidates affected diagnostics; +10. stale diagnostics never apply by nearest-text search, keyword search, or silent position repair. + +Diagnostics remain advisory. Their presence does not block form submission, email sending, export, or persistence in the editor package. + +## Selected architecture + +```mermaid +flowchart LR + H[Host review service] -->|revision-bound diagnostics| P[Inkspan public props] + P --> V[Deterministic diagnostic validator] + V --> D[ProseMirror decorations] + D --> U[Accessible diagnostics UI] + U --> A{Author action} + A -->|Apply| R[Revision/selector revalidation] + A -->|Ignore or Dismiss| C[Privacy-minimized callback] + R -->|match| T[Normal ProseMirror transaction] + R -->|stale or ambiguous| X[Typed conflict/invalidation] + T --> E[Normal onChange/onDocumentChange/undo] +``` + +The host may be Naruon, another CWL product, or an unrelated consumer. No host name appears in the runtime API. + +## Public contract + +The exact implementation names may be refined during planning, but the semantic contract is fixed. + +```ts +export type CwlWritingDiagnosticPriority = 'suggestion' | 'important'; + +export interface CwlWritingDiagnosticSelector { + readonly type: 'TextPositionSelector'; + readonly start: number; + readonly end: number; +} + +export interface CwlWritingDiagnosticProvenance { + readonly workflowId: string; + readonly workflowVersion: string; + readonly policyVersion: string; + readonly providerName?: string; + readonly modelName?: string; +} + +export interface CwlWritingDiagnostic { + readonly diagnosticId: string; + readonly documentRevision: string; + readonly projectionName: 'inkspan-prosemirror-text'; + readonly projectionVersion: 1; + readonly selector: CwlWritingDiagnosticSelector; + readonly categoryCode: string; + readonly priority: CwlWritingDiagnosticPriority; + readonly title: string; + readonly explanation: string; + readonly suggestedReplacement?: string; + readonly confidence?: number; + readonly provenance: CwlWritingDiagnosticProvenance; +} + +export type CwlWritingDiagnosticAction = + | 'apply' + | 'ignore' + | 'dismiss' + | 'explain'; + +export interface CwlWritingDiagnosticActionEvent { + readonly diagnosticId: string; + readonly action: CwlWritingDiagnosticAction; + readonly status: 'completed' | 'stale' | 'conflict' | 'rejected'; + readonly currentRevision?: string; + readonly reasonCode?: string; +} +``` + +Candidate props: + +```ts +interface CwlEditorProps { + writingDiagnostics?: readonly CwlWritingDiagnostic[]; + onWritingDiagnosticAction?: ( + event: CwlWritingDiagnosticActionEvent, + ) => void; +} +``` + +Candidate imperative method for hosts that render their own panel: + +```ts +interface CwlEditorHandle { + applyWritingDiagnosticIfMatch( + diagnosticId: string, + ): Promise; +} +``` + +The component and imperative paths must call the same implementation. There cannot be a “trusted imperative” bypass. + +## Validation boundary + +The diagnostic validator is deterministic and fail-closed. It verifies: + +- the collection is an array within a documented maximum count; +- every object contains exactly the supported fields; +- identifiers and category codes satisfy bounded syntax contracts; +- identifiers are unique within the supplied collection; +- text fields are non-empty where required and within documented limits; +- confidence, if present, is finite and in `[0, 1]`; +- the projection name and version are supported; +- the declared revision has valid Inkspan strong-entity-tag syntax; +- selector values are non-negative integers with `start < end`; +- selector boundaries are valid Unicode-code-point and grapheme-cluster boundaries; +- the range exists in the declared projection; +- replacement content passes the existing editor input, link, image, and schema policies; +- diagnostics do not contain executable markup or hidden event handlers; +- a bounded batch application contains no overlapping edits. + +The validator does not decide whether an explanation is true, whether a replacement is grammatically better, or whether a message is polite. Regexes may validate identifiers and revision syntax but cannot create or admit a semantic diagnostic based on source wording. + +## Revision and position lifecycle + +### Initial admission + +The host captures one document revision and text projection, sends that material through its review system, and returns diagnostics carrying the same revision and projection identity. Inkspan compares those fields with the editor state before rendering the proposals as current. + +### Local edits + +ProseMirror can map a range through transactions. Inkspan may keep a diagnostic current only when all of the following hold: + +- the original revision was admitted; +- every intervening transaction exposes a valid mapping; +- the mapped range is not deleted, split ambiguously, or replaced by unrelated content; +- the host's declared policy allows mapped presentation; +- application still performs a fresh current-state check. + +A mapped decoration is presentation convenience, not permission to apply stale model output. The final replacement action verifies the active state under the implementation plan's exact conflict contract. + +### Remote collaborative edits + +Yjs collaboration can remap local ProseMirror positions, but a model proposal remains bound to the original strong revision. A remote edit that changes the reviewed content invalidates the proposal for application. Inkspan must not treat a Yjs relative position as proof that the semantic target remained unchanged. + +### Re-review + +The host receives stale/conflict callbacks and may request a new review. Inkspan itself performs no network call and has no retry loop. + +## Decoration and interaction model + +- Different categories may use distinct underline patterns, but color alone is insufficient. +- Hover may show a preview, but every operation must be keyboard reachable. +- The editor toolbar remains one composite tab stop; diagnostic navigation may be a separate named toolbar or panel with a documented roving-tabindex pattern. +- Opening a diagnostic card does not move the caret unless the author explicitly chooses to navigate to the affected range. +- Applying a replacement creates one normal ProseMirror transaction and one normal undo step. +- After Apply, focus returns predictably to the editor at the end of the inserted replacement unless the host chooses a documented alternative. +- New asynchronous diagnostics must not steal focus or close a card the author is actively reading. +- Screen-reader output identifies category, ordinal position, affected range context, and available actions without reading the entire document. + +## Host feedback surface + +Inkspan reports action metadata only. The default event contains no selected source text, replacement text, explanation, prompt, raw model output, email recipient, or tenant identifier. + +A host that needs richer audit evidence must deliberately read it from its own authorized review-session store. This prevents generic analytics from becoming a shadow copy of authored documents. + +Recommended action reason codes include: + +```text +revision_mismatch +projection_mismatch +range_deleted +range_ambiguous +replacement_rejected +batch_overlap +unsupported_diagnostic +editor_destroyed +``` + +Reason codes are stable machine data. Human-readable failure messages remain localized host/editor UI text. + +## Security and privacy + +- Treat every diagnostic field as attacker-controlled input. +- Render title and explanation as text, not trusted HTML. +- Route replacements through existing safe-link, safe-image, clipboard, and schema policy. +- Do not allow a diagnostic to carry commands, JavaScript, arbitrary TipTap JSON, or host callbacks. +- Do not place source or replacement text in logs, exceptions, analytics, or performance marks. +- Do not expose provider credentials or full provider traces through provenance. +- Bound diagnostic count, text lengths, selector sizes, and decoration work to prevent rendering denial of service. +- Reject duplicate identifiers and unsupported fields rather than accepting ambiguous objects. +- Preserve Inkspan's no-runtime-environment-read and no-network-call contracts. + +## Keyword-judgment prohibition + +Inkspan must contain no semantic rule such as: + +```text +if text includes "무슨 말씀이신가요" then category = "tone" +if text includes "당황스럽습니다" then priority = "important" +if sender domain ends with X then apply business-language rule Y +``` + +Test fixtures will include: + +- the same phrase quoted neutrally and used as a direct rebuke; +- the same pragmatic problem expressed with unrelated vocabulary; +- intentionally misspelled words inside code, quotations, and proper names; +- recipient metadata that changes the host's interpretation while the draft text remains identical. + +Inkspan must produce zero diagnostics in every fixture unless the host explicitly supplies them. This proves the package is a renderer and integrity boundary, not a hidden classifier. + +## Failure behavior + +| Condition | Inkspan behavior | +|---|---| +| No diagnostics supplied | Normal editor behavior | +| Host review pending | Normal editor; optional host-owned loading UI | +| Host review failed | Normal editor; no fabricated fallback | +| Malformed diagnostic | Reject diagnostic collection or invalid entry according to the typed contract; no mutation | +| Stale revision | Mark invalid/stale; Apply unavailable; emit callback | +| Unsupported projection | Reject; no nearest-text recovery | +| Hostile explanation/replacement | Render safely or reject under existing policy | +| Overlapping batch | Reject batch; allow individually revalidated actions | +| Editor destroyed | Return typed non-mutating result | + +## Testing strategy + +### Pure contract tests + +- exact field, type, length, and count validation; +- duplicate IDs and unexpected fields; +- finite confidence and revision syntax; +- Unicode code-point ranges and grapheme boundaries; +- immutable/frozen public event snapshots where applicable; +- overlap detection and deterministic ordering. + +### Editor tests + +- decorations on exact ranges; +- local transaction mapping and invalidation; +- stale application rejection; +- safe replacement and one-step undo; +- no mutation on rejected input; +- action callback content minimization; +- no diagnostic generation from source text. + +### Accessibility tests + +- keyboard navigation and all actions; +- named regions and controls; +- focus restoration; +- polite live status; +- non-color-only rendering; +- arrival of new diagnostics while focus remains stable. + +### Collaborative tests + +- local and remote edits; +- Yjs remapping followed by revision rejection; +- no awareness publication caused by diagnostics; +- standalone/collaborative public API parity. + +### Package and browser tests + +- packed ESM/CommonJS/type consumers; +- React 18 and 19 host builds; +- SSR/hydration; +- Chromium, Firefox, and WebKit behavior; +- production statement, branch, function, and line coverage at exactly 100%; +- public declarations and JSDoc completeness. + +## Performance constraints + +- Validation is linear in diagnostic count plus bounded text projection work. +- Decoration updates are incremental where ProseMirror supports it. +- A configurable hard maximum prevents unbounded diagnostic decorations. +- No source document clone or SHA-256 digest is repeated merely to render an already-admitted set. +- Applying one proposal does not serialize the full document more times than required by the existing revision guard. +- Performance telemetry records counts and timing buckets, not authored text. + +## Documentation updates required with implementation + +- root README and React editor examples; +- public API declarations and JSDoc; +- selection lifecycle and revision evidence guides; +- accessibility guide; +- collaboration guide; +- security/privacy guidance; +- package distribution and packed-consumer verification; +- ADR index and documentation-fitness traceability; +- CHANGELOG and release evidence. + +## Out of scope + +- model invocation or model selection; +- spelling dictionaries or grammar models; +- email/thread/recipient semantics; +- host policy or submission blocking; +- persistent review sessions; +- diagnostic aggregation across users; +- human-review assignment; +- provider billing and retention; +- training or calibrating an LLM judge. + +Those responsibilities belong to the host or separate CWL services. + +## Primary references + +- W3C Web Annotation Data Model Recommendation for Unicode-code-point `TextPositionSelector` semantics and its warning that positions are brittle across resource changes. +- TipTap v2 and ProseMirror documentation for immutable editor state, transactions, selections, decorations, and mapping. +- RFC 9110 for strong entity-tag and conditional-write semantics used by Inkspan's revision boundary. +- Inkspan ADR 0011 for the deterministic versus model-assisted authoring boundary. +- Inkspan ADR 0018 for revision-scoped W3C selector authority. +- The accompanying doctoring record for LLM-judge bias and host calibration implications. + +## Approval boundary + +Approval of this design authorizes an implementation plan, not production claims. The feature remains unshipped until protected `main` contains the implementation, documentation, exact 100% coverage evidence, packed-package verification, cross-engine evidence, security checks, review approval, and release reconciliation. \ No newline at end of file From 55e3dae6bbc277c0b58bc88b666b1edcca98d5dd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 09:10:47 +0900 Subject: [PATCH 03/18] docs(doctoring): ground writing diagnostics in judge measurement evidence --- ...riting-diagnostics-measurement-boundary.md | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 docs/doctoring/llm-writing-diagnostics-measurement-boundary.md diff --git a/docs/doctoring/llm-writing-diagnostics-measurement-boundary.md b/docs/doctoring/llm-writing-diagnostics-measurement-boundary.md new file mode 100644 index 00000000..09de0e31 --- /dev/null +++ b/docs/doctoring/llm-writing-diagnostics-measurement-boundary.md @@ -0,0 +1,111 @@ +# LLM writing diagnostics: measurement and editor-boundary doctoring + +**Status:** Proposed design evidence; no shipped Inkspan diagnostic feature is claimed. +**Date:** 2026-08-12 + +## Decision supported by this record + +Inkspan may display and safely apply host-supplied writing diagnostics, but it must not infer grammar, tone, pragmatics, or technical quality from source text. Semantic judgment belongs to a host-owned LLM review and calibration system. Inkspan owns deterministic revision, selector, validation, accessibility, and mutation integrity. + +This division is not merely packaging preference. LLM evaluators are useful but behave like fallible measurement instruments. Their scores can vary with model, rubric, language, answer order, verbosity, formatting, self-provenance, and irrelevant surface artifacts. A deterministic editor cannot turn those outputs into truth; it can ensure only that a proposal is displayed and applied to the correct document state. + +## Evidence synthesis + +### LLMs can produce useful structured evaluations + +G-Eval demonstrated that rubric-driven, form-filling LLM evaluation can align more closely with human judgments than traditional automatic NLG metrics in the studied summarization setting. MT-Bench and Chatbot Arena likewise established LLM-as-a-Judge as a scalable evaluation method while explicitly documenting position, verbosity, self-enhancement, and reasoning limitations. + +**Product implication:** a host can use an LLM to generate criterion-level writing diagnostics, but the output must remain structured, versioned, and reviewable rather than becoming an opaque whole-document rewrite. + +### Evaluator output is biased and context-dependent + +Subsequent work has shown that LLM evaluators are not uniformly fair or stable. Reported problems include position bias, self-preference, disagreement across evaluation dimensions, sensitivity to apologetic or verbose artifacts, persuasion, auxiliary guidance, and multilingual inconsistency. Larger models do not automatically remove these problems. + +**Product implication:** Inkspan must not interpret a model confidence value as editor authority. The host must own judge calibration, multi-model or independent verification when warranted, abstention, monitoring, and human review. + +### Multilingual writing guidance requires language-specific evidence + +Multilingual judge studies report substantial inconsistency across languages and poorer behavior in some lower-resource settings. A feature that works in English cannot claim equivalent Korean, Japanese, Chinese, Vietnamese, or code-switched reliability merely because the underlying model accepts those languages. + +**Product implication:** Inkspan's selector, grapheme, accessibility, and rendering contracts must be language-neutral, while the host's semantic quality claims remain language-profile-specific and empirically validated. + +### Psychometric treatment is appropriate + +Recent psychometric work argues that a judge should be characterized as a measuring device rather than reported only by scalar agreement. Relevant properties include baseline response under null inputs, response to controlled quality ladders, surface cross-sensitivity, positional false preference, criterion movement, reliability, and calibration. IRT-based evaluation research likewise shows why item difficulty and discrimination matter and why fixed average scores can hide poor items or unstable rankings. + +**Product implication:** the editor API carries policy and provenance versions but does not perform calibration. A host such as Naruon may use fast-mlsirm to turn criterion-level polytomous judge responses into calibrated evaluation evidence. + +### Position selectors need revision state + +The W3C Web Annotation Data Model defines `TextPositionSelector` with Unicode-code-point offsets, inclusive start, exclusive end, logical text order, and a recommendation not to split grapheme clusters. It also warns that position selectors are brittle when a resource changes and recommends additional state. + +**Product implication:** Inkspan binds every writing diagnostic to its own strong document revision and declared projection. It does not recover a stale suggestion by keyword search, nearest-text matching, or positional guessing. + +## Deterministic validation is not semantic classification + +The design permits deterministic code for: + +- identifier and strong-revision syntax; +- exact JSON/schema validation; +- resource bounds and duplicate rejection; +- Unicode, selector, and grapheme boundaries; +- safe-link, inline-image, clipboard, and document-schema enforcement; +- current-revision comparison and transaction mapping; +- overlap detection and ordinary undo. + +The design prohibits deterministic code from asserting that prose is impolite, unclear, grammatically wrong, technically imprecise, or non-actionable based on lexical triggers. Regex validation of an identifier is materially different from a keyword-based language judgment. + +## Governance implications + +The host's LLM review system should record, at minimum: + +- model and provider identifiers; +- prompt, rubric, and policy versions; +- orchestration mode and reasoning configuration; +- criterion definitions and ordered category anchors; +- calibration dataset and language profile; +- observed agreement, false-positive rate, calibration error, and drift; +- abstention and escalation policy; +- privacy, retention, and provider data-use controls. + +Inkspan records only bounded diagnostic/action metadata by default. Source text, replacement text, prompts, raw model output, and document envelopes are not generic telemetry. + +## Standards alignment + +- **W3C Web Annotation Data Model:** interoperable text selector semantics and change brittleness. +- **NIST AI 600-1:** lifecycle-oriented Generative AI risk identification, evaluation, monitoring, and trustworthiness controls. +- **ISO/IEC 23894:2023:** integration of AI-specific risk management into organizational activities. +- **ISO/IEC 42001:2023:** management-system requirements for responsible AI development and use, including traceability and continual improvement. +- **AERA/APA/NCME Standards:** validity, reliability, fairness, intended-use, and consequences evidence for score-based decisions. Inkspan itself does not claim conformance to a psychological testing standard; the principles inform host judge validation. + +## APA 7th references + +American Educational Research Association, American Psychological Association, & National Council on Measurement in Education. (2014). *Standards for educational and psychological testing*. American Educational Research Association. + +Autio, C., Schwartz, R., Dunietz, J., Jain, S., Stanley, M., Tabassi, E., Hall, P., & Roberts, K. (2024). *Artificial intelligence risk management framework: Generative artificial intelligence profile* (NIST AI 600-1). National Institute of Standards and Technology. https://doi.org/10.6028/NIST.AI.600-1 + +Chen, H., & Goldfarb-Tarrant, S. (2025). Safer or luckier? LLMs as safety evaluators are not robust to artifacts. In *Proceedings of the 63rd Annual Meeting of the Association for Computational Linguistics (Volume 1: Long Papers)* (pp. 19750–19766). Association for Computational Linguistics. https://doi.org/10.18653/v1/2025.acl-long.970 + +Fu, X., & Liu, W. (2025). How reliable is multilingual LLM-as-a-Judge? In *Findings of the Association for Computational Linguistics: EMNLP 2025*. Association for Computational Linguistics. https://aclanthology.org/2025.findings-emnlp.587/ + +International Organization for Standardization. (2023a). *Information technology—Artificial intelligence—Guidance on risk management* (ISO/IEC Standard No. 23894:2023). https://www.iso.org/standard/77304.html + +International Organization for Standardization. (2023b). *Information technology—Artificial intelligence—Management system* (ISO/IEC Standard No. 42001:2023). https://www.iso.org/standard/42001.html + +Liu, S., Xu, Z., Liu, Z., Yan, Y., Yu, M., Gu, Y., Chen, C., Xie, H., & Yu, G. (2026). Mitigating judgment preference bias in large language models through group-based polling. In *Findings of the Association for Computational Linguistics: ACL 2026* (pp. 1448–1464). Association for Computational Linguistics. https://doi.org/10.18653/v1/2026.findings-acl.71 + +Liu, Y., Iter, D., Xu, Y., Wang, S., Xu, R., & Zhu, C. (2023). G-Eval: NLG evaluation using GPT-4 with better human alignment. In *Proceedings of the 2023 Conference on Empirical Methods in Natural Language Processing* (pp. 2511–2522). Association for Computational Linguistics. https://doi.org/10.18653/v1/2023.emnlp-main.153 + +Shen, C., Cheng, L., Nguyen, X.-P., You, Y., & Bing, L. (2023). Large language models are not yet human-level evaluators for abstractive summarization. In *Findings of the Association for Computational Linguistics: EMNLP 2023* (pp. 4215–4233). Association for Computational Linguistics. https://doi.org/10.18653/v1/2023.findings-emnlp.278 + +Usami, H., Hara, K., Tsuboi, A., & Matsuda, N. (2026). *LLM judges have dark current: A psychometric datasheet for LLM-as-a-Judge evaluation* [Preprint]. arXiv. https://arxiv.org/abs/2606.15610 + +Wang, P., Li, L., Chen, L., Cai, Z., Zhu, D., Lin, B., Cao, Y., Kong, L., Liu, Q., Liu, T., & Sui, Z. (2024). Large language models are not fair evaluators. In *Proceedings of the 62nd Annual Meeting of the Association for Computational Linguistics (Volume 1: Long Papers)* (pp. 9440–9450). Association for Computational Linguistics. https://doi.org/10.18653/v1/2024.acl-long.511 + +World Wide Web Consortium. (2017). *Web annotation data model*. https://www.w3.org/TR/annotation-model/ + +Zheng, L., Chiang, W.-L., Sheng, Y., Zhuang, S., Wu, Z., Zhuang, Y., Lin, Z., Li, Z., Li, D., Xing, E. P., Zhang, H., Gonzalez, J. E., & Stoica, I. (2023). *Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena* [Preprint]. arXiv. https://arxiv.org/abs/2306.05685 + +## Claim boundary + +These sources support the need for structured, calibrated, bias-aware host evaluation and revision-bound editor integrity. They do not prove that any particular model, rubric, language, or Naruon implementation is sufficiently accurate. Those claims require the implementation plan's task-specific benchmark and current-model evidence. \ No newline at end of file From 8fc023bb3d5139efac289965b83982dda35df228 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 09:16:58 +0900 Subject: [PATCH 04/18] docs(adr): index host-owned writing diagnostics --- docs/adr/README.md | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/docs/adr/README.md b/docs/adr/README.md index df8b7b80..fbd520a2 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -2,7 +2,7 @@ Status values: Proposed, Accepted, Superseded. -This index records durable architectural decisions. Protected-main implementation is required before a feature-specific decision may be treated as shipped behavior. Detailed ADR files are authoritative for their decision; this index is authoritative for discovery and status. +This index records durable architectural decisions. An ADR on a branch or protected `main` is not shipped behavior by status alone. `Accepted` requires protected-main implementation or process authority plus applicable verification evidence. | ADR | Status | Decision | |---|---|---| @@ -32,29 +32,16 @@ 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 | +| [0027](0027-host-owned-llm-writing-diagnostics.md) | Proposed | Host-owned, revision-bound LLM writing diagnostics | ## Decision discipline -- **Proposed**: documented decision whose acceptance evidence is incomplete or whose governing implementation/operational boundary is still being validated; not protected-main implementation authority by status alone. -- **Accepted**: decision has protected-main implementation or process authority with the applicable verification evidence and its canonical ADR status has been reconciled to that authority. -- **Superseded**: retained for historical traceability but explicitly replaced by a later ADR. +- **Proposed:** the decision is documented, but implementation or operational acceptance evidence is incomplete. +- **Accepted:** protected `main` contains the governing implementation or process and its verification evidence is current. +- **Superseded:** the ADR remains for history but a later ADR replaces it. -ADR decision status and implementation maturity are related but distinct. An ADR file may be present on protected `main` while its decision remains Proposed; conversely, an implemented capability can expose stale documentation until the ADR is reconciled. Canonical fitness and traceability records state implementation maturity explicitly, and an ADR cannot promote an unmerged feature to shipped behavior. - -When a decision changes materially, add or supersede an ADR rather than silently rewriting history. +Material changes add or supersede an ADR rather than silently rewriting history. ## ADR quality requirements -Every material ADR records explicit sections for: - -1. context and the problem boundary; -2. materially distinct alternatives considered; -3. the selected decision; -4. consequences and ownership trade-offs; -5. failure and recovery semantics; -6. security and privacy impact; -7. compatibility and migration behavior; -8. verification/acceptance evidence; and -9. rollback or explicit supersession conditions. - -Accessibility, operability, research/standards traceability, and release implications are included wherever the decision affects them. Canonical PRD, TRD, Architecture, contracts, UML, data/evidence model, threat model, test strategy, operability, and traceability documents must remain synchronized with Accepted decisions. +Each material ADR covers context, alternatives, decision, consequences, failure and recovery, security and privacy, compatibility and migration, verification, and rollback or supersession. Accessibility, operability, standards/research traceability, release impact, and synchronized PRD/TRD/Architecture/contracts/UML/data model/threat model/test strategy are required where affected. From 7c6d442272c80ae298fa5426f858568b955503ad Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 09:36:56 +0900 Subject: [PATCH 05/18] docs(plan): add writing diagnostics implementation plan --- ...8-12-writing-diagnostics-implementation.md | 458 ++++++++++++++++++ 1 file changed, 458 insertions(+) create mode 100644 docs/superpowers/plans/2026-08-12-writing-diagnostics-implementation.md diff --git a/docs/superpowers/plans/2026-08-12-writing-diagnostics-implementation.md b/docs/superpowers/plans/2026-08-12-writing-diagnostics-implementation.md new file mode 100644 index 00000000..27517d31 --- /dev/null +++ b/docs/superpowers/plans/2026-08-12-writing-diagnostics-implementation.md @@ -0,0 +1,458 @@ +# Revision-Bound Writing Diagnostics Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add a generic, provider-neutral Inkspan surface that validates, displays, navigates, applies, ignores, dismisses, and invalidates host-supplied writing diagnostics without making any semantic judgment itself. + +**Architecture:** A React-free `writing-diagnostics` contract validates bounded host input and resolves revision-scoped W3C text selectors against Inkspan's canonical text projection. A ProseMirror extension renders verified ranges and clears them on every document-changing transaction. A shared React controller binds asynchronous revision verification to one editor generation, while a built-in accessible panel exposes explicit actions. Standalone and collaborative editors reuse the same contract, controller, extension, and action result types. Version 1 accepts plain-text replacements only and never searches for similar text after a revision mismatch. + +**Tech Stack:** TypeScript, React 18/19, TipTap/ProseMirror, Yjs collaboration, Web Crypto revision evidence, W3C `TextPositionSelector`, Vitest with jsdom, Playwright across Chromium/Firefox/WebKit, Vite package subpath builds, pnpm, and the existing exact-head coverage/package/release gates. + +## Global Constraints + +- Inkspan does not call an LLM, provider, network service, database, storage service, or host API. +- Inkspan never infers grammar, spelling, tone, clarity, pragmatics, technical quality, or actionability from text. +- Keywords, regular expressions, phrase dictionaries, sender domains, language names, recipient counts, nearest-text search, and word positions are prohibited as semantic fallback or stale-selector repair. +- Version 1 replacements are plain text. HTML, commands, editor JSON, JavaScript, and arbitrary ProseMirror transactions are not accepted from a diagnostic. +- The canonical selector projection remains `inkspan-prosemirror-text` version `1`; a new projection requires a separate ADR and compatibility plan. +- Any document-changing local or collaborative transaction invalidates every active diagnostic before it can be applied. Version 1 does not retain or remap a diagnostic across a changed document. +- A diagnostic is actionable only after its declared strong revision, projection identity, selector, and grapheme boundaries have been verified against one exact editor snapshot. +- Asynchronous revision checks use generation tokens and never publish results for a replaced editor, destroyed editor, newer diagnostic set, or changed document. +- Default hard limits: + - 256 diagnostics per editor snapshot; + - 256 characters per `diagnosticId`; + - 128 characters per `categoryCode` and provenance identifier; + - 256 characters per title; + - 4,000 characters per explanation; + - 20,000 characters per replacement; + - confidence in the closed interval `[0, 1]`. +- Action callbacks and default telemetry-safe result objects contain opaque identifiers, revisions, category, action, bounded reason codes, and timing state only. They do not contain selected source text, replacement text, explanation, prompt, model output, document envelope, credential, or tenant identifier. +- Diagnostics remain advisory. Inkspan does not block form submission, persistence, export, or sending. +- Existing editor behavior is byte-for-byte and interaction-compatible when `writingDiagnostics` is absent. +- Production statement, branch, function, and line coverage remains exactly 100%. +- Public APIs and every shipped module/class/function receive beginner-readable documentation. +- Feature work remains under `Unreleased`; a separate exact-head release-only PR publishes the next compatible minor version after all acceptance gates pass. + +--- + +## Task 1: Define the React-Free Diagnostic Contract + +**Files:** +- Create: `src/writingDiagnostics.ts` +- Create: `src/writingDiagnostics.test.ts` +- Create: `src/writing-diagnostics/index.ts` +- Modify: `src/index.ts` + +- [ ] Write failing tests for valid diagnostics, empty arrays, duplicate IDs, unexpected fields, inherited fields, accessors, symbols, proxy exceptions, oversized arrays/strings, invalid confidence, unsupported priority, malformed revision, unsupported projection, invalid selector order, and non-string replacement values. +- [ ] Define the public v1 types: + +```ts +export type CwlWritingDiagnosticPriority = + | 'advisory' + | 'important' + | 'critical'; + +export interface CwlWritingDiagnostic { + readonly diagnosticId: string; + readonly documentRevision: CwlEditorDocumentRevision; + readonly textProjection: CwlEditorTextProjectionIdentity; + readonly selector: CwlEditorTextPositionSelector; + readonly categoryCode: string; + readonly priority: CwlWritingDiagnosticPriority; + readonly title: string; + readonly explanation: string; + readonly suggestedReplacement?: string; + readonly confidence?: number; + readonly provenance: Readonly<{ + workflowId: string; + workflowVersion: string; + judgePolicyVersion: string; + orchestrationMode?: string; + }>; +} +``` + +- [ ] Define stable, redacted error codes and `WritingDiagnosticError` for contract, limit, revision, projection, selector, conflict, and lifecycle failures. +- [ ] Define frozen default limits and a strict `validateWritingDiagnostics(input, limits?)` function that returns a deeply detached, deeply frozen tuple. +- [ ] Validate only own data properties. Catch hostile object/proxy behavior and return a stable error without reflecting source values. +- [ ] Reject duplicate diagnostic IDs and reject any object whose exact field set differs from the v1 schema. +- [ ] Preserve diagnostic order from the host; do not sort by confidence, category, wording, or source position inside the validator. +- [ ] Export only the React-free contract, validator, constants, limits, and error types from `src/writing-diagnostics/index.ts`. +- [ ] Re-export the same contract from the root package for interactive consumers. +- [ ] Run: + +```bash +pnpm exec vitest run src/writingDiagnostics.test.ts +pnpm typecheck +``` + +- [ ] Commit: + +```bash +git add src/writingDiagnostics.ts src/writingDiagnostics.test.ts src/writing-diagnostics/index.ts src/index.ts +git commit -m "feat(diagnostics): add strict writing diagnostic contract" +``` + +## Task 2: Add an Inverse Canonical Text-Projection Resolver + +**Files:** +- Create: `src/writingDiagnosticProjection.ts` +- Create: `src/writingDiagnosticProjection.test.ts` +- Modify: `src/textPositionSelectorEvidence.ts` +- Modify: `src/text-position-selector/index.ts` + +- [ ] Write failing tests that resolve selectors over paragraphs, headings, lists, tables, hard breaks, inline text, non-text leaf nodes, astral characters, Korean/CJK text, combining marks, emoji sequences, bidirectional text, empty blocks, and document boundaries. +- [ ] Add negative tests for unsupported projection versions, negative/non-integral offsets, reversed ranges, out-of-range offsets, grapheme-splitting boundaries, ambiguous projection boundaries, and runtimes without `Intl.Segmenter`. +- [ ] Implement a single-pass `buildTextProjectionMap(documentNode)` that emits the exact same text as `documentNode.textBetween(0, documentNode.content.size, '\n', '\uFFFC')` plus a boundary map between Unicode-code-point offsets and ProseMirror positions. +- [ ] Assert in tests that the independently built projection is exactly equal to the existing canonical projection for every fixture and generated document. +- [ ] Add: + +```ts +export function resolveTextPositionSelector( + documentNode: ProseMirrorNode, + selector: CwlEditorTextPositionSelector, + textProjection: CwlEditorTextProjectionIdentity, +): Readonly<{ from: number; to: number }>; +``` + +- [ ] Fail closed when a code-point boundary cannot map to one unambiguous ProseMirror position; never pick the nearest sentence, matching word, or repeated substring. +- [ ] Reuse one shared grapheme-boundary implementation for selector creation and resolution so forward and inverse paths cannot diverge. +- [ ] Add property tests that create a valid structural selection, convert it with `createTextPositionSelector()`, resolve it back, and recover the exact original range. +- [ ] Run: + +```bash +pnpm exec vitest run src/textPositionSelectorEvidence.test.ts src/writingDiagnosticProjection.test.ts +pnpm typecheck +``` + +- [ ] Commit: + +```bash +git add src/textPositionSelectorEvidence.ts src/text-position-selector/index.ts src/writingDiagnosticProjection.ts src/writingDiagnosticProjection.test.ts +git commit -m "feat(diagnostics): resolve revision-scoped text selectors" +``` + +## Task 3: Build the ProseMirror Decoration and Invalidation Extension + +**Files:** +- Create: `src/extensions/WritingDiagnostics.ts` +- Create: `src/extensions/WritingDiagnostics.test.ts` +- Modify: `src/extensions/kit.ts` + +- [ ] Write failing extension-state tests for installing a verified diagnostic set, inline decoration attributes, collapsed/empty ranges, duplicate install generations, clear commands, editor destruction, and document-changing transactions. +- [ ] Define one plugin key and typed transaction metadata for `install`, `focus`, and `clear` operations. +- [ ] Render verified non-empty ranges with safe static attributes only: + +```text +class="cwl-writing-diagnostic cwl-writing-diagnostic--{priority}" +data-cwl-diagnostic-id="opaque-id" +aria-invalid="spelling" only when the host category explicitly maps to mechanics +``` + +- [ ] Do not inject title, explanation, replacement, category text, model output, or HTML into decoration attributes. +- [ ] Clear all decorations and focused-diagnostic state on every `transaction.docChanged`, including Yjs remote transactions. +- [ ] Do not map a diagnostic through a changed document in version 1. +- [ ] Expose typed helper commands that accept already-validated, already-resolved ranges; the extension itself must not hash documents or call host code. +- [ ] Add the extension exactly once through `buildExtensions()` for standalone and collaborative editor graphs. +- [ ] Prove by source and runtime tests that an editor with no diagnostics has no added visual output, action surface, or document mutation. +- [ ] Run: + +```bash +pnpm exec vitest run src/extensions/WritingDiagnostics.test.ts src/extensions/kit.test.ts +pnpm typecheck +``` + +- [ ] Commit: + +```bash +git add src/extensions/WritingDiagnostics.ts src/extensions/WritingDiagnostics.test.ts src/extensions/kit.ts +git commit -m "feat(diagnostics): add fail-closed editor decorations" +``` + +## Task 4: Implement the Revision-Bound Controller State Machine + +**Files:** +- Create: `src/components/useWritingDiagnosticsController.ts` +- Create: `src/components/useWritingDiagnosticsController.test.tsx` +- Modify: `src/types.ts` + +- [ ] Write failing hook tests for initial verification, replacement diagnostic props, same-array identity with mutated hostile members, editor replacement, editor destruction, revision mismatch, projection mismatch, verification rejection, document change during hashing, overlapping asynchronous requests, and callback replacement without editor recreation. +- [ ] Define controller states: + +```text +absent -> verifying -> active + -> invalid +active -> applying -> applied +active -> ignored | dismissed | explanation_requested +active -> stale on any document change +``` + +- [ ] Add public action/result types with stable reason codes and no authored text: + +```ts +export type CwlWritingDiagnosticAction = + | 'applied' + | 'ignored' + | 'dismissed' + | 'requested_explanation' + | 'stale' + | 'conflict'; +``` + +- [ ] Validate diagnostics before reading the editor. +- [ ] Capture one immutable document envelope and derive one strong revision from that same snapshot before resolving any selector. +- [ ] Compare the declared revision and projection exactly; no normalization or compatibility guess is permitted. +- [ ] Resolve all ranges against the same immutable snapshot and reject the complete set atomically if structural validation fails. +- [ ] Treat overlapping diagnostics as displayable but prevent overlapping replacements from being batch-applied. Version 1 exposes single application only. +- [ ] Subscribe to editor transactions and immediately invalidate the active generation before scheduling any new verification. +- [ ] Use monotonic generation IDs and mounted/editor identity guards so older promises cannot install decorations, emit actions, or change focus. +- [ ] Contain host callback exceptions and keep editor state deterministic. +- [ ] Run: + +```bash +pnpm exec vitest run src/components/useWritingDiagnosticsController.test.tsx +pnpm typecheck +``` + +- [ ] Commit: + +```bash +git add src/components/useWritingDiagnosticsController.ts src/components/useWritingDiagnosticsController.test.tsx src/types.ts +git commit -m "feat(diagnostics): bind diagnostics to exact editor revisions" +``` + +## Task 5: Add Accessible Diagnostic Navigation and Action UI + +**Files:** +- Create: `src/components/WritingDiagnosticsPanel.tsx` +- Create: `src/components/WritingDiagnosticsPanel.test.tsx` +- Modify: `src/components/EditorFrame.tsx` +- Modify: `src/styles.css` + +- [ ] Write failing accessibility tests for a named region, count summary, ordered diagnostic list, category/priority/title/explanation, affected-range focus, previous/next navigation, Apply/Ignore/Dismiss/Explain actions, live status, and disabled application when no replacement exists. +- [ ] Add tests proving that asynchronous diagnostic arrival does not move focus and that explicit navigation returns focus predictably between the editor range and panel card. +- [ ] Add tests proving information remains available without color, hover, pointer input, animation, or generated CSS content. +- [ ] Render host strings as React text nodes only. Never use `dangerouslySetInnerHTML`. +- [ ] Give every action an explicit accessible name that includes the diagnostic title but does not copy the selected source passage into an attribute. +- [ ] Use buttons for previous/next navigation and roving focus within the list; do not add undocumented global shortcuts in version 1. +- [ ] Add one polite live region for completed actions and one assertive alert only for an application conflict. +- [ ] Add priority-specific underline styles plus forced-colors, high-contrast, reduced-motion, print, touch-target, and focus-visible rules. +- [ ] In print, omit action buttons and include a compact diagnostic appendix only when the host explicitly enables `printWritingDiagnostics`; default print output remains document-only. +- [ ] Add `writingDiagnosticsPanel?: ReactNode` support to `EditorFrame` only as an internally constructed trusted component slot; hosts do not inject raw diagnostic markup. +- [ ] Run: + +```bash +pnpm exec vitest run src/components/WritingDiagnosticsPanel.test.tsx src/components/EditorFrame.test.tsx src/printStyles.test.ts +pnpm typecheck +``` + +- [ ] Commit: + +```bash +git add src/components/WritingDiagnosticsPanel.tsx src/components/WritingDiagnosticsPanel.test.tsx src/components/EditorFrame.tsx src/styles.css +git commit -m "feat(diagnostics): add accessible writing guidance UI" +``` + +## Task 6: Integrate Standalone Editor Props and Imperative Actions + +**Files:** +- Modify: `src/types.ts` +- Modify: `src/components/CwlEditor.tsx` +- Modify: `src/components/useEditorHandle.ts` +- Modify: `src/components/CwlEditor.test.tsx` +- Modify: `src/components/CwlEditor.accessibility.test.tsx` +- Modify: `src/components/useEditorHandle.test.tsx` +- Create: `src/components/CwlEditor.writingDiagnostics.test.tsx` + +- [ ] Write failing integration tests for omitted props, valid diagnostics, invalid diagnostics, stale revisions, editor updates, Apply/Ignore/Dismiss/Explain, clear, undo, host callback replacement, read-only mode, disabled editor, form submission, and unmount. +- [ ] Add optional props: + +```ts +writingDiagnostics?: readonly CwlWritingDiagnostic[]; +onWritingDiagnosticAction?: (event: CwlWritingDiagnosticActionEvent) => void; +onWritingDiagnosticsError?: (error: WritingDiagnosticError) => void; +writingDiagnosticsLabel?: string; +printWritingDiagnostics?: boolean; +``` + +- [ ] Preserve the original diagnostics array by identity until the controller performs bounded validation; editor construction must not deeply inspect hostile values. +- [ ] Add imperative methods for focus, ignore, dismiss, explanation request, and asynchronous apply. Every method returns a typed result instead of throwing for ordinary stale/conflict outcomes. +- [ ] Apply one plain-text replacement through an ordinary ProseMirror transaction only after a second exact-current-revision check immediately before mutation. +- [ ] Compute and return the resulting strong revision from the applied post-transaction document, and emit the host callback only after the result is complete. +- [ ] Ensure the transaction enters the normal undo history and does not bypass clipboard, link, image, schema, or document-envelope policy. +- [ ] Immediately invalidate every remaining diagnostic after a successful apply. +- [ ] Keep editing, form submission, conversion, save, and export available when diagnostics are invalid, unavailable, stale, ignored, or unhandled. +- [ ] Prove a document containing words such as `rude`, `incorrect`, `urgent`, or their multilingual equivalents produces zero diagnostics unless the host supplies them. +- [ ] Run: + +```bash +pnpm exec vitest run src/components/CwlEditor.writingDiagnostics.test.tsx src/components/CwlEditor.accessibility.test.tsx src/components/useEditorHandle.test.tsx +pnpm typecheck +``` + +- [ ] Commit: + +```bash +git add src/types.ts src/components/CwlEditor.tsx src/components/useEditorHandle.ts src/components/CwlEditor.test.tsx src/components/CwlEditor.accessibility.test.tsx src/components/useEditorHandle.test.tsx src/components/CwlEditor.writingDiagnostics.test.tsx +git commit -m "feat(diagnostics): integrate revision-safe editor actions" +``` + +## Task 7: Establish Collaborative Editor Parity + +**Files:** +- Modify: `src/collaboration/CollaborativeCwlEditor.tsx` +- Modify: `src/collaboration/index.ts` +- Modify: `src/collaboration/index.test.ts` +- Modify: `src/collaboration/CollaborativeCwlEditor.test.tsx` +- Modify: `src/collaboration/CollaborativeCwlEditor.accessibility.test.tsx` +- Create: `src/collaboration/CollaborativeCwlEditor.writingDiagnostics.test.tsx` + +- [ ] Write failing tests for the same public props/actions as standalone Inkspan. +- [ ] Add a two-client Yjs test proving a remote insertion invalidates the local client's complete diagnostic set before any application can occur. +- [ ] Add a race test in which remote content changes while the local revision digest is pending; the older digest must not install decorations. +- [ ] Reuse `useWritingDiagnosticsController`; do not create a second collaborative-specific semantic or lifecycle implementation. +- [ ] Ensure awareness payloads never contain diagnostics, explanations, replacements, selected text, model provenance, or review state. +- [ ] Ensure remote action callbacks are not fabricated: only the client whose user explicitly invoked an action emits that action. +- [ ] Prove editor/provider/Yjs identity remains stable when diagnostics or callbacks change. +- [ ] Run: + +```bash +pnpm exec vitest run src/collaboration/CollaborativeCwlEditor.writingDiagnostics.test.tsx src/collaboration/CollaborativeCwlEditor.accessibility.test.tsx src/collaboration/index.test.ts +pnpm typecheck +``` + +- [ ] Commit: + +```bash +git add src/collaboration/CollaborativeCwlEditor.tsx src/collaboration/index.ts src/collaboration/index.test.ts src/collaboration/CollaborativeCwlEditor.test.tsx src/collaboration/CollaborativeCwlEditor.accessibility.test.tsx src/collaboration/CollaborativeCwlEditor.writingDiagnostics.test.tsx +git commit -m "feat(diagnostics): guarantee collaborative parity" +``` + +## Task 8: Publish a Framework-Neutral Package Subpath + +**Files:** +- Modify: `package.json` +- Modify: `pnpm-lock.yaml` +- Create: `vite.writing-diagnostics.config.ts` +- Modify: `scripts/verify-package.mjs` +- Modify: `src/packageExports.test.ts` +- Modify: `src/packageConsumer.test.ts` +- Create: `src/writing-diagnostics/packageBoundary.test.ts` + +- [ ] Add `@contextualwisdomlab/cwl-editor/writing-diagnostics` as a React-free ESM/CommonJS/type subpath. +- [ ] Keep UI components and editor handles on the root and collaboration entrypoints; the subpath exports only types, limits, validation, errors, and selector-resolution primitives that do not require React. +- [ ] Add a dedicated Vite build configuration and package export map entries. +- [ ] Add dependency-graph tests proving the subpath has no React, TipTap React, Yjs provider, model SDK, network, credential, filesystem, or database import. +- [ ] Extend package verification to install the packed tarball in isolated strict TypeScript ESM and CommonJS consumers and compile a complete diagnostic fixture. +- [ ] Verify CSS remains opt-in through the existing `styles.css` export and the pure subpath has no CSS side effect. +- [ ] Run: + +```bash +pnpm build +pnpm verify:package +pnpm exec vitest run src/packageExports.test.ts src/packageConsumer.test.ts src/writing-diagnostics/packageBoundary.test.ts +``` + +- [ ] Commit: + +```bash +git add package.json pnpm-lock.yaml vite.writing-diagnostics.config.ts scripts/verify-package.mjs src/packageExports.test.ts src/packageConsumer.test.ts src/writing-diagnostics/packageBoundary.test.ts +git commit -m "build(diagnostics): publish framework-neutral contracts" +``` + +## Task 9: Add Browser, SSR, Hostile-Input, and No-Fallback Assurance + +**Files:** +- Create: `tests/browser/specs/writing-diagnostics.browser.spec.ts` +- Modify: `tests/browser/fixture/index.html` +- Create: `src/components/writingDiagnosticsSecurity.test.tsx` +- Modify: `src/components/editorServerRendering.test.tsx` +- Modify: `docs/TEST_STRATEGY.md` + +- [ ] Add Playwright scenarios in Chromium, Firefox, and WebKit for rendering, keyboard navigation, range focus, apply, undo, stale invalidation, zoom, forced-colors, and mobile/touch action targets. +- [ ] Add SSR tests proving a deterministic initial shell renders without `window`, `document`, `Intl.Segmenter`, model infrastructure, or diagnostics evaluation. +- [ ] Add hydration tests proving diagnostics verify after the client editor is ready without duplicate IDs, focus theft, or markup mismatch. +- [ ] Add hostile-input tests for HTML/script strings, bidi controls, isolated surrogates, nulls, oversized values, accessors, proxies, duplicate keys after JSON parsing, and callback exceptions. +- [ ] Add semantic contrast fixtures proving Inkspan itself has no keyword behavior: + - identical words in a quotation and direct statement; + - the same issue expressed through unrelated paraphrases; + - product names, code, URLs, and paths resembling spelling errors; + - Korean, English, mixed-language, and CJK documents. +- [ ] The expected Inkspan result for every contrast fixture without host diagnostics is an empty diagnostic surface. +- [ ] Document that these tests establish editor integrity and rendering parity, not the accuracy of any LLM or host rubric. +- [ ] Run the repository-pinned browser workflow command used by CI and record the exact browser package lock and artifact receipt. +- [ ] Commit: + +```bash +git add tests/browser/specs/writing-diagnostics.browser.spec.ts tests/browser/fixture/index.html src/components/writingDiagnosticsSecurity.test.tsx src/components/editorServerRendering.test.tsx docs/TEST_STRATEGY.md +git commit -m "test(diagnostics): prove browser and no-fallback assurance" +``` + +## Task 10: Reconcile Canonical Documentation and Traceability + +**Files:** +- Modify: `README.md` +- Modify: `ARCHITECTURE.md` +- Modify: `docs/PRD.md` +- Modify: `docs/TRD.md` +- Modify: `docs/API_CONTRACT.md` +- Modify: `docs/THREAT_MODEL.md` +- Modify: `docs/OPERABILITY.md` +- Modify: `docs/TRACEABILITY.md` +- Modify: `docs/adr/0027-host-owned-llm-writing-diagnostics.md` +- Modify: `docs/adr/README.md` +- Modify: `CHANGELOG.md` +- Modify: `src/documentationContracts.test.ts` + +- [ ] Add public examples for host-supplied diagnostics, exact revision capture, action callbacks, stale refresh, standalone and collaborative editors, SSR, and no-model degraded operation. +- [ ] Document the distinction between semantic authority and deterministic integrity. +- [ ] Document that `confidence` and `priority` are host evidence labels, not editor truth or submission policy. +- [ ] Add a threat-model section for prompt/model output as untrusted data, hostile diagnostic objects, stale selectors, replacement injection, overlap conflicts, telemetry leakage, focus attacks, and collaboration races. +- [ ] Add an operability section for review-unavailable state, invalid diagnostics, refresh ownership, callback failure, feature rollback, and no-network/offline behavior. +- [ ] Update traceability from ADR requirement to source module, test, browser evidence, package evidence, and release gate. +- [ ] Keep ADR 0027 `Proposed` until protected `main` contains the implementation and exact-head acceptance evidence; promote it in the release reconciliation PR, not prematurely. +- [ ] Record the feature under `Unreleased` without claiming Naruon integration, LLM quality, language validation, or publication. +- [ ] Add documentation contract tests that fail if keyword fallback, provider ownership, stale-repair, or send-gating claims reappear. +- [ ] Run: + +```bash +pnpm exec vitest run src/documentationContracts.test.ts +pnpm typecheck +``` + +- [ ] Commit: + +```bash +git add README.md ARCHITECTURE.md docs/PRD.md docs/TRD.md docs/API_CONTRACT.md docs/THREAT_MODEL.md docs/OPERABILITY.md docs/TRACEABILITY.md docs/adr/0027-host-owned-llm-writing-diagnostics.md docs/adr/README.md CHANGELOG.md src/documentationContracts.test.ts +git commit -m "docs(diagnostics): reconcile product and assurance contracts" +``` + +## Task 11: Exact-Head Acceptance and Merge + +- [ ] Rebase or merge the latest protected `main` without discarding valid concurrent changes. +- [ ] Run the complete repository test suite. +- [ ] Run `pnpm typecheck`. +- [ ] Run `pnpm coverage` and prove 100% production statement, branch, function, and line coverage. +- [ ] Run deterministic demo/library builds. +- [ ] Run `pnpm verify:package` against the packed tarball outside the source tree. +- [ ] Run cross-engine browser evidence through the repository-pinned Playwright lane. +- [ ] Run Office Python 3.11 and 3.14 test, docstring, branch coverage, wheel, schema, and license gates. +- [ ] Run SAST, dependency, supply-chain, SBOM, provenance, and secret checks on the exact final head. +- [ ] Review every current-head CodeRabbit, GitHub Advanced Security, Dependabot, OpenCode, Noema, Strix, human, and other applicable finding. +- [ ] Resolve every valid review thread and rerun affected tests. +- [ ] Confirm zero unresolved valid review threads. +- [ ] Obtain a qualifying non-author current-head approval. +- [ ] Move the PR from Draft to Ready only after implementation, direct validation, and documentation gates are complete. +- [ ] Merge without bypass only after all protected exact-head checks and approval rules pass. +- [ ] Refetch protected `main` and verify the merge commit contains the expected files and no unrelated branch artifacts. + +## Task 12: Release-Only Publication and Naruon Handoff + +- [ ] Open a separate release-only PR for the next compatible minor version after the feature merge. +- [ ] Promote ADR 0027 to `Accepted` only with protected-main implementation and exact-head evidence. +- [ ] Update version metadata, final CHANGELOG release section, package declarations, license inventory, SBOM, provenance, and rollback evidence. +- [ ] Publish immutable npm artifacts only from the exact reviewed release head. +- [ ] Verify ESM, CommonJS, types, CSS, root, collaboration, and `writing-diagnostics` subpaths from the published package. +- [ ] Record the immutable version, tarball integrity, source commit, package manifest, and compatibility matrix in the Naruon companion PR. +- [ ] Do not merge Naruon's runtime integration against a mutable Inkspan branch, source archive, local path, or unreviewed package. +- [ ] Retain a documented rollback path that removes diagnostic props and UI without document migration or canonical-envelope changes. From 225d98946abb6eb4059a604962d85bcb5952a8d0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 10:54:28 +0900 Subject: [PATCH 06/18] docs(adr): restore canonical quality contract --- docs/adr/README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/adr/README.md b/docs/adr/README.md index fbd520a2..3b73b384 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -44,4 +44,15 @@ Material changes add or supersede an ADR rather than silently rewriting history. ## ADR quality requirements -Each material ADR covers context, alternatives, decision, consequences, failure and recovery, security and privacy, compatibility and migration, verification, and rollback or supersession. Accessibility, operability, standards/research traceability, release impact, and synchronized PRD/TRD/Architecture/contracts/UML/data model/threat model/test strategy are required where affected. +Every material ADR documents the following evidence explicitly: + +- context and the problem boundary; +- materially distinct alternatives considered; +- the selected decision and its consequences; +- failure and recovery semantics; +- security and privacy impact; +- compatibility and migration behavior; +- verification/acceptance evidence; +- rollback or explicit supersession conditions. + +Accessibility, operability, standards/research traceability, release impact, and synchronized PRD/TRD/Architecture/contracts/UML/data model/threat model/test strategy are also required where affected. From ae0b1a2ef69b04789727d298b3dbf68d77427db4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 19:44:03 +0900 Subject: [PATCH 07/18] docs(adr): make writing diagnostics v1 lifecycle unambiguous --- ...ting-diagnostics-v1-strict-invalidation.md | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 docs/adr/0028-writing-diagnostics-v1-strict-invalidation.md diff --git a/docs/adr/0028-writing-diagnostics-v1-strict-invalidation.md b/docs/adr/0028-writing-diagnostics-v1-strict-invalidation.md new file mode 100644 index 00000000..d7e947be --- /dev/null +++ b/docs/adr/0028-writing-diagnostics-v1-strict-invalidation.md @@ -0,0 +1,100 @@ +# ADR 0028: Strict invalidation and semantic-neutral accessibility for writing diagnostics v1 + +Status: Proposed + +## Context + +ADR 0027, its design specification, and its implementation plan established the correct high-level boundary: hosts own every semantic writing judgment while Inkspan owns deterministic validation, revision/selector integrity, presentation, accessibility, and ordinary editor transactions. + +Two lower-level clauses nevertheless permitted incompatible first-release interpretations: + +1. some prose allowed a diagnostic to survive a local transaction when ProseMirror mapping appeared valid, while the implementation plan's global contract required every local or collaborative document change to invalidate every active diagnostic; and +2. the decoration plan proposed `aria-invalid="spelling"` when a host category “maps to mechanics,” although `categoryCode` is deliberately opaque and the v1 contract contains no explicit semantic accessibility field. + +Leaving those ambiguities unresolved would force implementers either to invent semantic mapping from an opaque string or to maintain two competing stale-diagnostic lifecycles. + +## Alternatives considered + +### Preserve diagnostics through transaction mapping + +Rejected for v1. ProseMirror can map structural positions, but position continuity does not prove that a host model's semantic judgment still applies to changed prose. This also conflicts with the already implemented controller/decorations direction and complicates collaborative edits, asynchronous digest races, application revalidation, testing, and host refresh ownership. + +### Derive ARIA validity from `categoryCode` + +Rejected. `categoryCode` is host-defined opaque metadata. Comparing it with strings such as `spelling`, `grammar`, `mechanics`, or language-specific equivalents would be the semantic keyword fallback that ADR 0027 explicitly prohibits. + +### Add an explicit semantic ARIA enum to v1 + +Deferred. A future version may add a bounded, explicitly declared accessibility semantic if evidence shows that decoration-level `aria-invalid` is interoperable and useful across supported browser/assistive-technology combinations. That addition requires its own versioned contract, tests, compatibility review, and ADR. + +### Strictly invalidate on every document change and keep decorations semantically neutral + +Selected for v1 because it is deterministic, explainable, provider-neutral, privacy-minimized, compatible with the existing implementation stack, and safe under both standalone and collaborative editing. + +## Decision + +For writing diagnostics v1: + +- every transaction with `docChanged === true`, whether local or collaborative, invalidates the complete active diagnostic generation before any further display or mutation authority can be used; +- no diagnostic range is preserved, remapped, repaired, or re-admitted through ProseMirror mapping, nearest-text search, quote search, keyword search, or semantic guessing; +- a host that wants current guidance after a document change must submit a new diagnostic set bound to a newly derived exact document revision; +- final application still performs exact current-state verification under the implementation plan, but mapping is not an alternate admission path; +- inline decorations contain static Inkspan classes, priority styling, and an opaque diagnostic identifier only; +- Inkspan does not derive `aria-invalid`, spelling/grammar state, or any other semantic accessibility assertion from `categoryCode`, title, explanation, replacement, confidence, provenance, or source text; +- category, priority, title, explanation, and actions remain available as plain text in the named diagnostics panel, while underlines are a visual supplement rather than the sole information channel. + +This ADR narrows and supersedes only the conflicting lifecycle and decoration-accessibility clauses in ADR 0027, the 2026-08-12 design specification, and the original implementation plan. Their product/host/model authority, security, privacy, revision, packaging, and release decisions remain in force. + +## Consequences + +### Positive + +- one lifecycle applies to local edits, remote edits, digest races, focus, action callbacks, and replacement application; +- no hidden semantic classifier is introduced into Inkspan; +- stale diagnostics fail closed without pretending position continuity proves meaning continuity; +- hosts receive a clear refresh responsibility; +- accessibility remains complete through explicit panel text and actions without unsupported semantic ARIA claims. + +### Trade-offs + +- even an unrelated document edit invalidates all active diagnostics in v1; +- hosts may perform more review refreshes; +- Inkspan does not preserve diagnostic continuity across edits until a future, separately governed evidence model exists. + +These costs are accepted because deterministic invalidation is safer and easier to validate than a partially semantic remapping policy. + +## Failure and recovery + +- A document-changing transaction clears active decorations and marks the generation stale. +- A digest or selector result completing for an invalidated generation is discarded. +- Apply/ignore/dismiss/explain operations against a missing or stale generation return typed non-mutating results. +- The host may recover only by supplying a newly admitted diagnostic set for the current exact revision. +- No offline or model-unavailable fallback fabricates a diagnostic. + +## Security and privacy impact + +The decision prevents opaque host category strings from gaining semantic execution or accessibility authority. It also prevents stale model output from being moved onto changed content. No additional authored text, replacement text, prompt, model output, tenant identifier, provider credential, or document envelope enters telemetry or DOM attributes. + +## Accessibility impact + +The panel must expose a named region, count, ordered list, category, priority, title, explanation, affected-range navigation, and explicit Apply/Ignore/Dismiss/Explain controls. Information must remain available without color, hover, pointer input, animation, or generated CSS content. Asynchronous arrival must not steal focus. An assertive alert is reserved for an actual application conflict; ordinary actions use polite status messaging. + +## Verification + +Acceptance requires deterministic tests proving: + +- local and Yjs remote `docChanged` transactions invalidate the complete generation; +- no transaction mapping preserves diagnostics in v1; +- stale asynchronous work cannot install decorations or emit actions; +- decoration attributes contain no title, explanation, replacement, category text, model output, HTML, or derived semantic ARIA state; +- diagnostics with category codes such as `spelling`, `grammar`, `mechanics`, multilingual equivalents, or attacker-controlled lookalikes receive identical semantic-neutral decoration handling; +- the accessible panel exposes host strings as text and remains usable without visual-only cues; +- Inkspan produces no diagnostic when the host supplies none. + +## Migration and rollback + +No document migration is required because diagnostics are noncanonical review state. Existing Task 1–4 implementation work already follows strict invalidation and semantic-neutral decoration behavior; downstream tasks must retain that contract. Rollback removes the optional diagnostic surface without altering document envelopes, revisions, persistence, or collaboration data. + +## Supersession + +A future diagnostic-continuity design may supersede strict invalidation only with a versioned evidence model that proves target identity across changes, defines standalone/collaborative parity, contains privacy and accessibility semantics, and passes cross-engine and realistic semantic-integrity validation. A future semantic ARIA field likewise requires an explicit typed contract rather than inference from opaque strings. From 2396561889cc495fbe8c20e90b8adecf3bb60108 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 19:44:33 +0900 Subject: [PATCH 08/18] docs(plan): bind diagnostics tasks to strict v1 semantics --- ...-writing-diagnostics-v1-contract-errata.md | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 docs/superpowers/plans/2026-08-12-writing-diagnostics-v1-contract-errata.md diff --git a/docs/superpowers/plans/2026-08-12-writing-diagnostics-v1-contract-errata.md b/docs/superpowers/plans/2026-08-12-writing-diagnostics-v1-contract-errata.md new file mode 100644 index 00000000..a377f872 --- /dev/null +++ b/docs/superpowers/plans/2026-08-12-writing-diagnostics-v1-contract-errata.md @@ -0,0 +1,71 @@ +# Writing Diagnostics v1 Implementation Plan Errata + +Status: Required companion to `2026-08-12-writing-diagnostics-implementation.md` + +ADR 0028 resolves two contradictions discovered after the original atomic implementation plan was written. Every remaining task and acceptance review must apply this errata. Task 10 must fold these corrections into the canonical ADR, design, plan, PRD, TRD, contracts, threat model, operability, traceability, and CHANGELOG so the final protected-main documentation has no parallel instruction set. + +## Global lifecycle correction + +The following rule replaces every original-plan or design clause that permits transaction-local preservation, mapping, remapping, repair, or re-admission of an existing diagnostic: + +> Any local or collaborative transaction with `docChanged === true` invalidates the complete active diagnostic generation. Version 1 never preserves or remaps a diagnostic across changed document content. A host must submit a new set bound to the new exact revision. + +ProseMirror mapping may be used internally for ordinary editor behavior, but it is not evidence that a model judgment still targets the same meaning and is not diagnostic admission authority. + +## Task 3 correction: decoration attributes + +Replace the proposed attribute set with: + +```text +class="cwl-writing-diagnostic cwl-writing-diagnostic--{priority}" +data-cwl-diagnostic-id="opaque-id" +``` + +Do not add `aria-invalid`, spelling/grammar state, category semantics, title, explanation, replacement, confidence, provenance, model output, or HTML to decoration attributes. `categoryCode` remains opaque; it cannot be matched against words such as `spelling`, `grammar`, or `mechanics` to derive behavior or ARIA state. + +Task 3 tests must include attacker-controlled, multilingual, and lookalike category codes and prove identical semantic-neutral decoration handling. + +## Task 4 correction: controller state + +The controller's transaction subscriber invalidates both `verifying` and `active` generations before stale asynchronous work can publish. It does not map verified ranges after any document change. All old digest/selector completions are generation-fenced and discarded. + +## Task 5 correction: accessible panel + +The named panel is the semantic accessibility surface. It exposes category, priority, title, explanation, count, ordered position, affected-range navigation, and explicit Apply/Ignore/Dismiss/Explain actions as React text and native controls. + +- Do not infer semantic ARIA state from `categoryCode` or any other host text. +- Do not place selected source text in action names or attributes. +- New asynchronous diagnostics do not move focus. +- Previous/next navigation is explicit and roving; no undocumented global shortcut is added. +- Ordinary action completion uses a polite status region. +- An assertive alert is reserved for an actual application conflict. +- Information remains available without color, hover, pointer input, animation, or generated CSS content. + +## Task 6 correction: application + +Apply rechecks the exact current document revision immediately before the ordinary ProseMirror transaction. A stale generation cannot be rescued through range mapping or text search. A successful application invalidates all remaining diagnostics and produces a newly derived resulting revision. + +## Task 7 correction: collaboration + +Every remote Yjs document change invalidates the complete local diagnostic generation. A relative position or mapped ProseMirror position is not proof that the host judgment remains semantically current. Awareness payloads never carry diagnostics or review state. + +## Task 9 correction: assurance + +Cross-engine and hostile-input evidence must prove: + +- strict invalidation after every local or remote document change; +- no nearest-text, quote, keyword, category, or transaction-mapping recovery; +- no semantic ARIA derivation from opaque host fields; +- accessible information remains present through the panel and native actions; +- no diagnostics are produced when the host supplies none. + +## Task 10 reconciliation requirement + +Before the feature stack can become Ready, the original ADR 0027 and design/plan prose must be edited so they directly express ADR 0028. This errata is temporary planning evidence, not the desired final duplicate source of truth. The final documentation contract tests must fail if either of the superseded claims reappears: + +1. a diagnostic can survive `docChanged` through transaction mapping; or +2. Inkspan derives `aria-invalid` or other semantic state from an opaque category string. + +## Acceptance impact + +Existing Task 1–4 runtime direction is compatible with this correction. Downstream Task 5–12 branches must be based on exact predecessor heads that contain or explicitly consume this errata, and all exact-head CI/review evidence must be regenerated after any affected branch is refreshed. From e580aec1961673b49f98e2a62debfb6ce4e48df6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 19:56:26 +0900 Subject: [PATCH 09/18] docs(adr): satisfy canonical decision quality contract --- ...028-writing-diagnostics-v1-strict-invalidation.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/adr/0028-writing-diagnostics-v1-strict-invalidation.md b/docs/adr/0028-writing-diagnostics-v1-strict-invalidation.md index d7e947be..10587ee0 100644 --- a/docs/adr/0028-writing-diagnostics-v1-strict-invalidation.md +++ b/docs/adr/0028-writing-diagnostics-v1-strict-invalidation.md @@ -79,6 +79,12 @@ The decision prevents opaque host category strings from gaining semantic executi The panel must expose a named region, count, ordered list, category, priority, title, explanation, affected-range navigation, and explicit Apply/Ignore/Dismiss/Explain controls. Information must remain available without color, hover, pointer input, animation, or generated CSS content. Asynchronous arrival must not steal focus. An assertive alert is reserved for an actual application conflict; ordinary actions use polite status messaging. +## Compatibility and migration + +The decision is additive to hosts that do not enable writing diagnostics. Diagnostics remain noncanonical review state, so no document-envelope, persistence, collaboration, or database migration is required. Existing Task 1–4 implementation work already follows strict invalidation and, after the corresponding runtime correction, semantic-neutral decoration behavior. Downstream tasks must consume the same exact contract; predecessor checks and reviews do not transfer after any stack refresh. + +A future public contract may add explicit accessibility semantics or evidence-backed diagnostic continuity only through a new versioned type and compatibility plan. Opaque v1 category strings do not acquire new meaning retrospectively. + ## Verification Acceptance requires deterministic tests proving: @@ -91,10 +97,8 @@ Acceptance requires deterministic tests proving: - the accessible panel exposes host strings as text and remains usable without visual-only cues; - Inkspan produces no diagnostic when the host supplies none. -## Migration and rollback - -No document migration is required because diagnostics are noncanonical review state. Existing Task 1–4 implementation work already follows strict invalidation and semantic-neutral decoration behavior; downstream tasks must retain that contract. Rollback removes the optional diagnostic surface without altering document envelopes, revisions, persistence, or collaboration data. +## Rollback or supersession -## Supersession +Rollback removes the optional diagnostic surface without altering canonical document envelopes, revisions, persistence records, collaboration data, or host storage. The host simply stops supplying diagnostics and the editor retains its ordinary deterministic behavior. A future diagnostic-continuity design may supersede strict invalidation only with a versioned evidence model that proves target identity across changes, defines standalone/collaborative parity, contains privacy and accessibility semantics, and passes cross-engine and realistic semantic-integrity validation. A future semantic ARIA field likewise requires an explicit typed contract rather than inference from opaque strings. From a8859210d5dd2ae29126e4c0233cbcb61c9146a1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 20:23:27 +0900 Subject: [PATCH 10/18] test(docs): require every ADR in the canonical index --- src/adrQualityContract.test.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/adrQualityContract.test.ts b/src/adrQualityContract.test.ts index 04dcef71..32664c2e 100644 --- a/src/adrQualityContract.test.ts +++ b/src/adrQualityContract.test.ts @@ -23,6 +23,8 @@ const requiredAdrHeadings = [ /^## Rollback or supersession(?:\b|\s|$)/mu, ] as const; +const ADR_STATUS = /^Status: (Proposed|Accepted|Superseded)$/mu; + describe('ADR quality documentation contract', () => { it('preserves the canonical ADR quality requirements on every reconciliation branch', () => { const adrIndex = repositoryFile('docs/adr/README.md'); @@ -37,6 +39,25 @@ describe('ADR quality documentation contract', () => { expect(adrIndex).toContain('rollback or explicit supersession conditions'); }); + it('indexes every detailed ADR with its exact file and status', () => { + const adrIndex = repositoryFile('docs/adr/README.md'); + const adrFiles = detailedAdrFiles(); + + expect(adrFiles.length).toBeGreaterThan(0); + + for (const adrFile of adrFiles) { + const adr = repositoryFile(`docs/adr/${adrFile}`); + const status = adr.match(ADR_STATUS)?.[1]; + const adrNumber = adrFile.slice(0, 4); + + expect(status, `${adrFile} has no canonical status`).toBeDefined(); + expect( + adrIndex, + `${adrFile} is missing from the ADR index or has a stale status`, + ).toContain(`| [${adrNumber}](${adrFile}) | ${status} |`); + } + }); + it('applies the canonical quality sections to every detailed ADR', () => { const adrFiles = detailedAdrFiles(); From 3b13654a4ed892a613e939e2fc24616c2b8cad42 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 20:30:04 +0900 Subject: [PATCH 11/18] docs(adr): index strict diagnostics v1 decision --- docs/adr/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/adr/README.md b/docs/adr/README.md index 3b73b384..8c314016 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -33,6 +33,7 @@ This index records durable architectural decisions. An ADR on a branch or protec | [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 | | [0027](0027-host-owned-llm-writing-diagnostics.md) | Proposed | Host-owned, revision-bound LLM writing diagnostics | +| [0028](0028-writing-diagnostics-v1-strict-invalidation.md) | Proposed | Strict writing-diagnostic invalidation and semantic-neutral accessibility | ## Decision discipline From 7d7638a51827692f30c907ae31c8cc690e69fbc0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 20:37:39 +0900 Subject: [PATCH 12/18] test(docs): bind diagnostics design to strict v1 contract --- ...ngDiagnosticsDocumentationContract.test.ts | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 src/writingDiagnosticsDocumentationContract.test.ts diff --git a/src/writingDiagnosticsDocumentationContract.test.ts b/src/writingDiagnosticsDocumentationContract.test.ts new file mode 100644 index 00000000..03053c44 --- /dev/null +++ b/src/writingDiagnosticsDocumentationContract.test.ts @@ -0,0 +1,99 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +import { describe, expect, it } from 'vitest'; + +/** Read one authoritative repository text artifact for deterministic assertions. */ +function repositoryFile(path: string): string { + return readFileSync(resolve(process.cwd(), path), 'utf8'); +} + +/** Collapse prose whitespace so Markdown wrapping cannot weaken the contract. */ +function normalizeProse(value: string): string { + return value.replace(/\s+/gu, ' ').trim(); +} + +const designPath = + 'docs/superpowers/specs/2026-08-12-revision-bound-llm-writing-diagnostics-design.md'; +const planPath = + 'docs/superpowers/plans/2026-08-12-writing-diagnostics-implementation.md'; +const adrPath = 'docs/adr/0027-host-owned-llm-writing-diagnostics.md'; +const supersedingAdrPath = + 'docs/adr/0028-writing-diagnostics-v1-strict-invalidation.md'; + +describe('writing diagnostics documentation contract', () => { + it('keeps the public design examples synchronized with the implemented v1 types', () => { + const design = repositoryFile(designPath); + + expect(design).toContain( + "export type CwlWritingDiagnosticPriority =\n | 'advisory'\n | 'important'\n | 'critical';", + ); + expect(design).toContain( + 'readonly documentRevision: CwlEditorDocumentRevision;', + ); + expect(design).toContain( + 'readonly textProjection: CwlEditorTextProjectionIdentity;', + ); + expect(design).toContain('readonly judgePolicyVersion: string;'); + expect(design).toContain( + 'readonly reasonCode: CwlWritingDiagnosticActionReasonCode;', + ); + expect(design).toContain('readonly generation: number;'); + expect(design).not.toContain( + "export type CwlWritingDiagnosticPriority = 'suggestion' | 'important';", + ); + expect(design).not.toContain('readonly documentRevision: string;'); + expect(design).not.toContain( + "readonly projectionName: 'inkspan-prosemirror-text';", + ); + expect(design).not.toContain("readonly status: 'completed'"); + }); + + it('requires strict invalidation and semantic-neutral decoration guidance everywhere', () => { + const design = normalizeProse(repositoryFile(designPath)); + const plan = repositoryFile(planPath); + const adr = normalizeProse(repositoryFile(adrPath)); + const supersedingAdr = normalizeProse(repositoryFile(supersedingAdrPath)); + + const strictInvalidation = + 'Every local or collaborative transaction with docChanged === true invalidates the complete active diagnostic generation.'; + const semanticNeutrality = + 'Inkspan does not derive aria-invalid or any other semantic accessibility state from opaque host strings.'; + + expect(design).toContain(strictInvalidation); + expect(adr).toContain(strictInvalidation); + expect(supersedingAdr).toContain( + 'every transaction with docChanged === true, whether local or collaborative, invalidates the complete active diagnostic generation', + ); + expect(design).toContain(semanticNeutrality); + expect(adr).toContain(semanticNeutrality); + + expect(design).not.toContain('Inkspan may keep a diagnostic current'); + expect(design).not.toContain('local transaction mapping and invalidation'); + expect(design).not.toContain('Yjs remapping followed by revision rejection'); + expect(adr).not.toContain( + 'decoration mapping through local ProseMirror transactions', + ); + expect(adr).not.toContain('A safely mapped local transaction may preserve'); + expect(adr).not.toContain('valid transaction-local mapping'); + expect(plan).not.toContain('aria-invalid="spelling"'); + }); + + it('documents collapsed selectors and one-action-at-a-time application without batch authority', () => { + const design = normalizeProse(repositoryFile(designPath)); + const adr = normalizeProse(repositoryFile(adrPath)); + + expect(design).toContain( + 'selector values are non-negative safe integers with start <= end', + ); + expect(design).toContain( + 'Version 1 applies exactly one explicitly selected diagnostic at a time.', + ); + expect(adr).toContain( + 'Version 1 applies exactly one explicitly selected diagnostic at a time.', + ); + expect(design).not.toContain('a bounded batch application'); + expect(design).not.toContain('batch_overlap'); + expect(adr).not.toContain('Apply all'); + }); +}); From d68232aafb6ad94411d0c9d2994a1f9fdb28bf40 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 20:45:28 +0900 Subject: [PATCH 13/18] docs(diagnostics): reconcile the strict v1 design contract --- ...on-bound-llm-writing-diagnostics-design.md | 393 ++++++++---------- 1 file changed, 179 insertions(+), 214 deletions(-) diff --git a/docs/superpowers/specs/2026-08-12-revision-bound-llm-writing-diagnostics-design.md b/docs/superpowers/specs/2026-08-12-revision-bound-llm-writing-diagnostics-design.md index 8e753eb2..75e26e15 100644 --- a/docs/superpowers/specs/2026-08-12-revision-bound-llm-writing-diagnostics-design.md +++ b/docs/superpowers/specs/2026-08-12-revision-bound-llm-writing-diagnostics-design.md @@ -1,4 +1,4 @@ -# Revision-Bound LLM Writing Diagnostics Design +# Revision-Bound Writing Diagnostics Design **Date:** 2026-08-12 **Status:** Proposed design; not shipped behavior @@ -6,99 +6,108 @@ ## Objective -Add a provider-neutral, Grammarly-like writing-diagnostic surface to Inkspan without turning Inkspan into a language model, email product, policy engine, or persistence service. +Add a provider-neutral, Grammarly-like writing-diagnostic surface without turning Inkspan into a language model, email product, policy engine, persistence service, or hidden semantic classifier. -A host application will generate contextual writing proposals using an LLM and its own review policy. Inkspan will display those proposals against the exact document revision from which they were generated, let the author inspect and apply or ignore each one, and prevent stale asynchronous output from mutating newer content. +A host application generates contextual writing proposals using its own model, rubric, authorization, privacy, retention, and review policy. Inkspan admits only structurally valid host proposals, binds them to one exact document revision and text projection, renders them accessibly, and permits explicit user actions without allowing stale asynchronous output to mutate changed content. -The feature must support spelling, grammar, spacing, punctuation, clarity, concision, structure, tone, pragmatics, technical precision, and actionability as host-defined categories. Inkspan does not determine any of those categories. It exposes a generic review contract and deterministic document integrity. +Host-defined categories may describe spelling, grammar, spacing, punctuation, clarity, concision, structure, tone, pragmatics, technical precision, or actionability. Inkspan treats those fields as opaque proposal data. It does not determine whether a category, explanation, confidence, priority, or replacement is semantically correct. ## Product behavior -An author sees normal Inkspan editing first. When the host supplies diagnostics: +An author sees normal Inkspan editing first. When the host supplies an admitted diagnostic set: -1. affected ranges receive non-color-only decorations; -2. the diagnostics summary reports the number and categories of suggestions; -3. keyboard and pointer users can move to the previous or next suggestion; -4. a suggestion card explains the issue and shows an optional replacement; -5. Apply changes only the selected range; -6. Ignore reports a host-visible feedback action without changing the document; -7. Dismiss removes the local presentation until the host changes the diagnostic set; -8. Explain requests no model call from Inkspan; it reveals the explanation already supplied by the host or invokes a host callback; -9. any document change revalidates or invalidates affected diagnostics; -10. stale diagnostics never apply by nearest-text search, keyword search, or silent position repair. +1. non-empty affected ranges receive non-color-only visual decorations; +2. a named diagnostics region reports the count and exposes an ordered list; +3. each item exposes category, priority, title, explanation, and optional replacement as plain text; +4. keyboard, pointer, touch, and assistive-technology users can navigate the previous or next diagnostic; +5. Focus moves to an affected structural range only after explicit user navigation; +6. Apply rechecks the exact current revision and applies one selected plain-text replacement through one ordinary ProseMirror transaction; +7. Ignore and Dismiss emit privacy-minimized host-visible actions without changing canonical content; +8. Explain reveals the supplied explanation or invokes an explicit host callback; Inkspan performs no model call; +9. asynchronous arrival never steals focus; and +10. stale diagnostics never apply through nearest-text search, keyword search, quote search, remapping, or silent position repair. -Diagnostics remain advisory. Their presence does not block form submission, email sending, export, or persistence in the editor package. +Diagnostics remain advisory. Their presence, absence, invalidity, or staleness does not block form submission, sending, export, persistence, or collaboration in the editor package. ## Selected architecture ```mermaid flowchart LR H[Host review service] -->|revision-bound diagnostics| P[Inkspan public props] - P --> V[Deterministic diagnostic validator] - V --> D[ProseMirror decorations] - D --> U[Accessible diagnostics UI] - U --> A{Author action} - A -->|Apply| R[Revision/selector revalidation] - A -->|Ignore or Dismiss| C[Privacy-minimized callback] - R -->|match| T[Normal ProseMirror transaction] - R -->|stale or ambiguous| X[Typed conflict/invalidation] - T --> E[Normal onChange/onDocumentChange/undo] + P --> V[Deterministic contract validator] + V --> R[Exact revision and selector resolver] + R --> D[Semantic-neutral ProseMirror decorations] + D --> U[Accessible diagnostics panel] + U --> A{Explicit author action} + A -->|Apply| C[Exact current-revision check] + A -->|Ignore Dismiss Explain| E[Privacy-minimized action event] + C -->|match| T[Ordinary ProseMirror transaction] + C -->|stale conflict| X[Typed non-mutating outcome] + T --> O[Normal change revision and undo behavior] ``` -The host may be Naruon, another CWL product, or an unrelated consumer. No host name appears in the runtime API. +The host may be Naruon, another CWL product, or an unrelated consumer. No host, provider, model, email, or tenant name appears in the generic runtime contract. ## Public contract -The exact implementation names may be refined during planning, but the semantic contract is fixed. +The v1 design mirrors the implementation types rather than maintaining a second approximate schema. ```ts -export type CwlWritingDiagnosticPriority = 'suggestion' | 'important'; - -export interface CwlWritingDiagnosticSelector { - readonly type: 'TextPositionSelector'; - readonly start: number; - readonly end: number; -} +export type CwlWritingDiagnosticPriority = + | 'advisory' + | 'important' + | 'critical'; export interface CwlWritingDiagnosticProvenance { readonly workflowId: string; readonly workflowVersion: string; - readonly policyVersion: string; - readonly providerName?: string; - readonly modelName?: string; + readonly judgePolicyVersion: string; + readonly orchestrationMode?: string; } export interface CwlWritingDiagnostic { readonly diagnosticId: string; - readonly documentRevision: string; - readonly projectionName: 'inkspan-prosemirror-text'; - readonly projectionVersion: 1; - readonly selector: CwlWritingDiagnosticSelector; + readonly documentRevision: CwlEditorDocumentRevision; + readonly textProjection: CwlEditorTextProjectionIdentity; + readonly selector: CwlEditorTextPositionSelector; readonly categoryCode: string; readonly priority: CwlWritingDiagnosticPriority; readonly title: string; readonly explanation: string; readonly suggestedReplacement?: string; readonly confidence?: number; - readonly provenance: CwlWritingDiagnosticProvenance; + readonly provenance: Readonly; } export type CwlWritingDiagnosticAction = - | 'apply' - | 'ignore' - | 'dismiss' - | 'explain'; + | 'applied' + | 'ignored' + | 'dismissed' + | 'requested_explanation' + | 'stale' + | 'conflict'; + +export type CwlWritingDiagnosticActionReasonCode = + | 'explicit' + | 'document_changed' + | 'revision_mismatch' + | 'projection_mismatch' + | 'selector_invalid' + | 'verification_failed' + | 'lifecycle_ended' + | 'diagnostic_missing'; export interface CwlWritingDiagnosticActionEvent { - readonly diagnosticId: string; readonly action: CwlWritingDiagnosticAction; - readonly status: 'completed' | 'stale' | 'conflict' | 'rejected'; - readonly currentRevision?: string; - readonly reasonCode?: string; + readonly reasonCode: CwlWritingDiagnosticActionReasonCode; + readonly diagnosticId: string; + readonly documentRevision: CwlEditorDocumentRevision; + readonly categoryCode: string; + readonly generation: number; } ``` -Candidate props: +Candidate additive editor props are: ```ts interface CwlEditorProps { @@ -106,235 +115,191 @@ interface CwlEditorProps { onWritingDiagnosticAction?: ( event: CwlWritingDiagnosticActionEvent, ) => void; + onWritingDiagnosticsError?: (error: WritingDiagnosticError) => void; + writingDiagnosticsLabel?: string; + printWritingDiagnostics?: boolean; } ``` -Candidate imperative method for hosts that render their own panel: +Candidate imperative methods use the same controller and validation path as the built-in panel. There is no trusted imperative bypass. -```ts -interface CwlEditorHandle { - applyWritingDiagnosticIfMatch( - diagnosticId: string, - ): Promise; -} -``` +## Validation boundary -The component and imperative paths must call the same implementation. There cannot be a “trusted imperative” bypass. +The deterministic validator fails closed and verifies: -## Validation boundary +- exact own enumerable data properties and no unsupported fields, symbols, accessors, sparse arrays, inherited fields, or hostile reflection; +- bounded collection size, identifier length, category length, title, explanation, replacement, and provenance identifiers; +- unique diagnostic identifiers within one submitted generation; +- supported finite priority and confidence values; +- an exact lowercase SHA-256 revision object and matching strong entity tag; +- the exact `inkspan-prosemirror-text` version 1 projection identity; +- selector values are non-negative safe integers with start <= end; +- Unicode-code-point and grapheme-cluster boundaries; +- one unambiguous structural range in the exact projected snapshot; and +- plain-text replacement values only. -The diagnostic validator is deterministic and fail-closed. It verifies: - -- the collection is an array within a documented maximum count; -- every object contains exactly the supported fields; -- identifiers and category codes satisfy bounded syntax contracts; -- identifiers are unique within the supplied collection; -- text fields are non-empty where required and within documented limits; -- confidence, if present, is finite and in `[0, 1]`; -- the projection name and version are supported; -- the declared revision has valid Inkspan strong-entity-tag syntax; -- selector values are non-negative integers with `start < end`; -- selector boundaries are valid Unicode-code-point and grapheme-cluster boundaries; -- the range exists in the declared projection; -- replacement content passes the existing editor input, link, image, and schema policies; -- diagnostics do not contain executable markup or hidden event handlers; -- a bounded batch application contains no overlapping edits. - -The validator does not decide whether an explanation is true, whether a replacement is grammatically better, or whether a message is polite. Regexes may validate identifiers and revision syntax but cannot create or admit a semantic diagnostic based on source wording. +Collapsed selectors are valid evidence and remain navigable, but they create no inline range decoration. Empty and non-empty selectors use the same exact revision/projection admission path. + +The validator does not decide whether an explanation is true, a replacement is better, a message is polite, or a category label is accurate. Regexes may validate bounded identifiers and revision syntax, but they cannot create, prioritize, admit, or semantically classify a diagnostic. ## Revision and position lifecycle ### Initial admission -The host captures one document revision and text projection, sends that material through its review system, and returns diagnostics carrying the same revision and projection identity. Inkspan compares those fields with the editor state before rendering the proposals as current. +The host reviews one immutable document snapshot and returns diagnostics carrying that snapshot's exact `CwlEditorDocumentRevision`, projection identity, and W3C text-position selector. Inkspan validates the complete untrusted set before reading editor state, derives one current revision from one immutable editor snapshot, resolves all selectors against that same snapshot, and publishes only a complete verified generation. -### Local edits +### Strict invalidation -ProseMirror can map a range through transactions. Inkspan may keep a diagnostic current only when all of the following hold: +Every local or collaborative transaction with docChanged === true invalidates the complete active diagnostic generation. -- the original revision was admitted; -- every intervening transaction exposes a valid mapping; -- the mapped range is not deleted, split ambiguously, or replaced by unrelated content; -- the host's declared policy allows mapped presentation; -- application still performs a fresh current-state check. +Version 1 does not preserve, map, remap, repair, or re-admit a diagnostic after changed document content. ProseMirror mapping and Yjs relative positions are useful editor mechanisms, but neither proves that a host model judgment remains semantically current. A stale async digest or selector result is discarded through the generation fence. The host must submit a new set bound to the new exact revision. -A mapped decoration is presentation convenience, not permission to apply stale model output. The final replacement action verifies the active state under the implementation plan's exact conflict contract. +### Explicit action -### Remote collaborative edits +Version 1 applies exactly one explicitly selected diagnostic at a time. -Yjs collaboration can remap local ProseMirror positions, but a model proposal remains bound to the original strong revision. A remote edit that changes the reviewed content invalidates the proposal for application. Inkspan must not treat a Yjs relative position as proof that the semantic target remained unchanged. +Immediately before application, Inkspan derives and compares the current exact revision again. A matching diagnostic with a valid plain-text replacement produces one ordinary ProseMirror transaction and normal undo history. A mismatch, missing diagnostic, invalid selector, ended lifecycle, or changed document returns a typed non-mutating event. Successful application invalidates every remaining diagnostic and derives the resulting revision from the post-transaction document. -### Re-review +There is no Apply All or batch mutation authority in v1. Overlapping diagnostics may be displayed independently, but each action is revalidated after every document change. -The host receives stale/conflict callbacks and may request a new review. Inkspan itself performs no network call and has no retry loop. +### Re-review -## Decoration and interaction model +The host may use a stale/conflict action event to request a new review. Inkspan performs no network call, retry, provider fallback, or diagnostic regeneration. -- Different categories may use distinct underline patterns, but color alone is insufficient. -- Hover may show a preview, but every operation must be keyboard reachable. -- The editor toolbar remains one composite tab stop; diagnostic navigation may be a separate named toolbar or panel with a documented roving-tabindex pattern. -- Opening a diagnostic card does not move the caret unless the author explicitly chooses to navigate to the affected range. -- Applying a replacement creates one normal ProseMirror transaction and one normal undo step. -- After Apply, focus returns predictably to the editor at the end of the inserted replacement unless the host chooses a documented alternative. -- New asynchronous diagnostics must not steal focus or close a card the author is actively reading. -- Screen-reader output identifies category, ordinal position, affected range context, and available actions without reading the entire document. +## Decoration and accessibility model -## Host feedback surface +Inline decorations contain only: -Inkspan reports action metadata only. The default event contains no selected source text, replacement text, explanation, prompt, raw model output, email recipient, or tenant identifier. +```text +class="cwl-writing-diagnostic cwl-writing-diagnostic--{priority}" +data-cwl-diagnostic-id="opaque-id" +``` -A host that needs richer audit evidence must deliberately read it from its own authorized review-session store. This prevents generic analytics from becoming a shadow copy of authored documents. +Inkspan does not derive aria-invalid or any other semantic accessibility state from opaque host strings. -Recommended action reason codes include: +The editor does not infer spelling, grammar, mechanics, tone, or correctness from `categoryCode`, title, explanation, replacement, confidence, provenance, or source text. It does not place those strings in decoration attributes. The named diagnostics panel is the semantic accessibility surface. -```text -revision_mismatch -projection_mismatch -range_deleted -range_ambiguous -replacement_rejected -batch_overlap -unsupported_diagnostic -editor_destroyed -``` +The panel must provide: -Reason codes are stable machine data. Human-readable failure messages remain localized host/editor UI text. +- a named region and count summary; +- an ordered list with category, priority, title, and explanation; +- explicit previous/next navigation with roving focus; +- an affected-range Focus action; +- Apply, Ignore, Dismiss, and Explain native buttons; +- a disabled Apply action when no replacement exists; +- a polite status region for ordinary completed actions; +- an assertive alert only for an actual application conflict; +- no focus theft when diagnostics arrive asynchronously; and +- equivalent information without color, hover, pointer input, animation, or generated CSS content. -## Security and privacy +Host strings render as React text nodes only. Action names may use the diagnostic title but never copy selected source text into DOM attributes. Underlines are a visual supplement, not the sole information channel. -- Treat every diagnostic field as attacker-controlled input. -- Render title and explanation as text, not trusted HTML. -- Route replacements through existing safe-link, safe-image, clipboard, and schema policy. -- Do not allow a diagnostic to carry commands, JavaScript, arbitrary TipTap JSON, or host callbacks. -- Do not place source or replacement text in logs, exceptions, analytics, or performance marks. -- Do not expose provider credentials or full provider traces through provenance. -- Bound diagnostic count, text lengths, selector sizes, and decoration work to prevent rendering denial of service. -- Reject duplicate identifiers and unsupported fields rather than accepting ambiguous objects. -- Preserve Inkspan's no-runtime-environment-read and no-network-call contracts. +## Host feedback surface -## Keyword-judgment prohibition +Default action events contain only opaque identifiers, exact revision evidence, opaque category code, generation, action, and bounded reason code. They contain no selected source text, replacement text, explanation, prompt, raw model output, email recipient, credential, document envelope, or tenant identifier. -Inkspan must contain no semantic rule such as: +A host requiring richer audit evidence reads it from its own authorized review-session store. Generic analytics must not become a shadow copy of authored content. -```text -if text includes "무슨 말씀이신가요" then category = "tone" -if text includes "당황스럽습니다" then priority = "important" -if sender domain ends with X then apply business-language rule Y -``` +## Security and privacy -Test fixtures will include: +- Treat every diagnostic object and host string as untrusted input. +- Reject accessors, prototypes, symbols, extra fields, sparse arrays, proxies, duplicate identifiers, and resource-limit violations. +- Render title, explanation, category, and replacement previews as text, never trusted HTML. +- Accept only plain-text replacement values in v1. +- Do not allow commands, JavaScript, arbitrary TipTap JSON, host callbacks, or executable markup inside a diagnostic. +- Do not place authored or model-produced text in logs, exceptions, analytics, performance marks, awareness payloads, or decoration attributes. +- Do not expose provider credentials, raw provider traces, or tenant data through provenance. +- Preserve Inkspan's no-runtime-environment-read and no-network-call contracts. -- the same phrase quoted neutrally and used as a direct rebuke; -- the same pragmatic problem expressed with unrelated vocabulary; -- intentionally misspelled words inside code, quotations, and proper names; -- recipient metadata that changes the host's interpretation while the draft text remains identical. +## Semantic keyword prohibition -Inkspan must produce zero diagnostics in every fixture unless the host explicitly supplies them. This proves the package is a renderer and integrity boundary, not a hidden classifier. +Inkspan must produce zero diagnostics unless the host explicitly supplies them. It contains no semantic rule based on keywords, regexes, phrase dictionaries, sender domains, recipient counts, language names, positions, or nearest-text similarity. Opaque values that happen to contain words such as `spelling`, `grammar`, `mechanics`, `rude`, `incorrect`, or multilingual equivalents do not gain behavior or semantic ARIA authority. ## Failure behavior | Condition | Inkspan behavior | |---|---| | No diagnostics supplied | Normal editor behavior | -| Host review pending | Normal editor; optional host-owned loading UI | -| Host review failed | Normal editor; no fabricated fallback | -| Malformed diagnostic | Reject diagnostic collection or invalid entry according to the typed contract; no mutation | -| Stale revision | Mark invalid/stale; Apply unavailable; emit callback | -| Unsupported projection | Reject; no nearest-text recovery | -| Hostile explanation/replacement | Render safely or reject under existing policy | -| Overlapping batch | Reject batch; allow individually revalidated actions | -| Editor destroyed | Return typed non-mutating result | +| Host review pending or unavailable | Normal editor; optional host-owned status UI | +| Malformed or oversized input | Reject complete set through a redacted typed error; no editor mutation | +| Revision mismatch | Do not install or apply; return bounded stale/conflict evidence | +| Unsupported projection | Reject; no nearest-text or compatibility recovery | +| Invalid or ambiguous selector | Reject complete set; no guessed position | +| Local or remote document change | Invalidate complete active generation immediately | +| Host callback throws | Contain callback failure; preserve deterministic editor state | +| Editor destroyed | Return typed non-mutating lifecycle evidence | ## Testing strategy -### Pure contract tests +### Contract and hostile-input tests -- exact field, type, length, and count validation; -- duplicate IDs and unexpected fields; -- finite confidence and revision syntax; -- Unicode code-point ranges and grapheme boundaries; -- immutable/frozen public event snapshots where applicable; -- overlap detection and deterministic ordering. +- exact fields, types, limits, priorities, confidence, revision, projection, and selector validation; +- duplicate identifiers, sparse arrays, accessors, inherited fields, symbols, proxies, and hostile reflection; +- Unicode astral characters, Korean/CJK, combining marks, emoji, bidirectional text, empty/collapsed selectors, and grapheme boundaries; +- immutable detached public values and redacted errors. -### Editor tests +### Editor and concurrency tests -- decorations on exact ranges; -- local transaction mapping and invalidation; -- stale application rejection; -- safe replacement and one-step undo; -- no mutation on rejected input; -- action callback content minimization; -- no diagnostic generation from source text. +- exact decoration attributes and no semantic ARIA derivation; +- local and remote `docChanged` invalidation; +- generation fencing for overlapping async verification; +- exact application recheck, ordinary transaction, resulting revision, and one-step undo; +- no mutation on rejected or stale input; +- privacy-minimized action events and callback-failure containment; +- zero diagnostics without host input. ### Accessibility tests -- keyboard navigation and all actions; -- named regions and controls; -- focus restoration; -- polite live status; -- non-color-only rendering; -- arrival of new diagnostics while focus remains stable. +- named region, ordered list, count, category, priority, title, and explanation; +- keyboard navigation, roving focus, and explicit actions; +- no focus theft, polite status, conflict alert, forced colors, high contrast, reduced motion, print, zoom, and touch targets; +- no visual-only or hover-only information. -### Collaborative tests +### Collaboration, package, and browser tests -- local and remote edits; -- Yjs remapping followed by revision rejection; -- no awareness publication caused by diagnostics; -- standalone/collaborative public API parity. +- standalone/collaborative API parity and remote invalidation; +- no diagnostics in Yjs awareness payloads; +- SSR-safe shell and deterministic hydration; +- packed ESM/CommonJS/strict-TypeScript consumers; +- React-free `writing-diagnostics` subpath; +- Chromium, Firefox, and WebKit evidence; and +- exact 100% owned production statement, branch, function, and line coverage plus complete public JSDoc. -### Package and browser tests +## Performance constraints -- packed ESM/CommonJS/type consumers; -- React 18 and 19 host builds; -- SSR/hydration; -- Chromium, Firefox, and WebKit behavior; -- production statement, branch, function, and line coverage at exactly 100%; -- public declarations and JSDoc completeness. +- Validation is linear in bounded diagnostic count and bounded projection size. +- At most 256 active diagnostics are admitted by default. +- One immutable snapshot and one revision derivation are shared across one verification generation. +- No document clone or digest is repeated merely to render an already admitted set. +- A document change invalidates rather than remaps the set, keeping v1 lifecycle cost deterministic. +- Telemetry records counts and timing buckets, not authored text. -## Performance constraints +## Documentation and release requirements + +Implementation must synchronize root README, public API/JSDoc, PRD, TRD, API contract, architecture, threat model, operability, selector/revision guides, collaboration guide, test strategy, ADR index, traceability, CHANGELOG, package consumers, SBOM/provenance, rollback, and release evidence. -- Validation is linear in diagnostic count plus bounded text projection work. -- Decoration updates are incremental where ProseMirror supports it. -- A configurable hard maximum prevents unbounded diagnostic decorations. -- No source document clone or SHA-256 digest is repeated merely to render an already-admitted set. -- Applying one proposal does not serialize the full document more times than required by the existing revision guard. -- Performance telemetry records counts and timing buckets, not authored text. - -## Documentation updates required with implementation - -- root README and React editor examples; -- public API declarations and JSDoc; -- selection lifecycle and revision evidence guides; -- accessibility guide; -- collaboration guide; -- security/privacy guidance; -- package distribution and packed-consumer verification; -- ADR index and documentation-fitness traceability; -- CHANGELOG and release evidence. +The feature remains `Unreleased`. It may ship only after the complete stack is reconciled onto protected main, temporary branch-specific workflows are removed, exact-head CI/security/coverage/package/browser evidence succeeds, zero valid findings remain, qualifying independent review exists, and a separate release-only PR publishes immutable artifacts. ## Out of scope -- model invocation or model selection; -- spelling dictionaries or grammar models; +- model invocation, model selection, prompt construction, rubric ownership, judge calibration, or provider failover; +- spelling dictionaries or deterministic grammar/tone classifiers; - email/thread/recipient semantics; -- host policy or submission blocking; -- persistent review sessions; -- diagnostic aggregation across users; -- human-review assignment; -- provider billing and retention; -- training or calibrating an LLM judge. - -Those responsibilities belong to the host or separate CWL services. +- host submission, send, persistence, or compliance gates; +- persistent review sessions and cross-user aggregation; +- human-review assignment; and +- provider billing, retention, or training. ## Primary references -- W3C Web Annotation Data Model Recommendation for Unicode-code-point `TextPositionSelector` semantics and its warning that positions are brittle across resource changes. -- TipTap v2 and ProseMirror documentation for immutable editor state, transactions, selections, decorations, and mapping. -- RFC 9110 for strong entity-tag and conditional-write semantics used by Inkspan's revision boundary. -- Inkspan ADR 0011 for the deterministic versus model-assisted authoring boundary. +- W3C Web Annotation Data Model for Unicode-code-point `TextPositionSelector` semantics and its warning that positions are brittle across resource changes. +- TipTap v2 and ProseMirror documentation for immutable editor state, transactions, selections, and decorations. +- RFC 9110 for strong entity-tag semantics used by Inkspan revision evidence. +- Inkspan ADR 0011 for deterministic versus model-assisted authoring. - Inkspan ADR 0018 for revision-scoped W3C selector authority. +- Inkspan ADR 0027 and ADR 0028 for host semantic authority, strict invalidation, and semantic-neutral accessibility. - The accompanying doctoring record for LLM-judge bias and host calibration implications. ## Approval boundary -Approval of this design authorizes an implementation plan, not production claims. The feature remains unshipped until protected `main` contains the implementation, documentation, exact 100% coverage evidence, packed-package verification, cross-engine evidence, security checks, review approval, and release reconciliation. \ No newline at end of file +Approval of this design authorizes implementation work, not production claims. The feature remains unshipped until protected main contains the reconciled implementation, canonical documentation, exact acceptance evidence, independent approval, and verified release artifacts. From 99e9bd3727acb4da79706ede32ab07d279d6aee5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 20:46:43 +0900 Subject: [PATCH 14/18] docs(adr): reconcile host-owned diagnostics authority --- ...0027-host-owned-llm-writing-diagnostics.md | 191 ++++++++++-------- 1 file changed, 102 insertions(+), 89 deletions(-) diff --git a/docs/adr/0027-host-owned-llm-writing-diagnostics.md b/docs/adr/0027-host-owned-llm-writing-diagnostics.md index 05d9e0f4..25e98da8 100644 --- a/docs/adr/0027-host-owned-llm-writing-diagnostics.md +++ b/docs/adr/0027-host-owned-llm-writing-diagnostics.md @@ -1,152 +1,165 @@ -# ADR 0027: Host-owned, revision-bound LLM writing diagnostics +# ADR 0027: Host-owned, revision-bound writing diagnostics Status: Proposed ## Context -Inkspan already provides deterministic authoring, revision evidence, revision-scoped W3C `TextPositionSelector` evidence, guarded restore, and a host-owned model-assistance boundary. Host applications now need a Grammarly-like writing-guidance surface that can underline a passage, explain a problem, propose a replacement, let the author apply or ignore it, and keep every suggestion bound to the exact document revision from which it was produced. +Inkspan already provides deterministic authoring, revision evidence, revision-scoped W3C `TextPositionSelector` evidence, guarded restore, and a host-owned model-assistance boundary. Host applications need a Grammarly-like writing-guidance surface that can mark a passage, explain a concern, propose a replacement, let the author act explicitly, and keep every proposal bound to the exact document revision reviewed by the host. -The requested quality judgments include spelling, grammar, clarity, concision, discourse structure, workplace pragmatics, audience appropriateness, technical precision, actionability, and preservation of the author's intended request. These are contextual language judgments. Fixed keyword lists, regular-expression phrase detectors, domain-suffix lists, hand-written “aggressive phrase” tables, and positional repair rules cannot establish those meanings and create brittle false positives and false negatives across paraphrases, quotations, languages, and recipient contexts. +Spelling, grammar, clarity, concision, discourse structure, workplace pragmatics, audience appropriateness, technical precision, actionability, and preservation of author intent are contextual language judgments. Fixed keyword lists, regular-expression phrase detectors, domain-suffix lists, hand-written phrase tables, opaque category names, and positional repair rules cannot establish those meanings across paraphrases, quotations, languages, and recipient contexts. -Inkspan must remain a modular editor. It must not gain email semantics, tenant policy, model credentials, network transport, an LLM provider dependency, or authority to decide whether a sentence is appropriate. At the same time, hosts should not have to scrape ProseMirror DOM nodes or maintain a private editor fork to display revision-safe diagnostics. +Inkspan must remain a modular editor. It must not gain email semantics, tenant policy, model credentials, network transport, an LLM provider dependency, persistence authority, or authority to decide whether prose is appropriate. Hosts likewise should not have to scrape ProseMirror DOM nodes or maintain private editor forks to display revision-safe guidance. ## Alternatives considered -- **Host-specific DOM overlays over Inkspan.** Rejected because DOM offsets are not a supported document contract, become stale after ProseMirror transactions, fragment keyboard and screen-reader behavior, and force every host to rebuild decorations, navigation, application, conflict handling, and undo semantics. -- **A deterministic keyword or regex checker inside Inkspan.** Rejected because lexical triggers are not evidence of grammar, intent, tone, pragmatics, or technical correctness. Deterministic code may validate data shape and document coordinates, but it may not manufacture semantic judgments. -- **Inkspan invokes an LLM directly.** Rejected because the editor would acquire provider, credential, network, privacy, retention, availability, model-routing, and tenant-policy responsibilities that belong to the host. -- **The host sends whole-document rewrites and calls `setValue`.** Rejected because whole-document replacement obscures individual reasons, weakens author control, destroys revision-local review evidence, and makes accidental intent changes harder to detect. -- **Host-owned LLM judgments rendered through a generic Inkspan diagnostic contract.** Selected because it preserves Inkspan's provider-neutral deterministic core while giving every host one revision-safe, accessible writing-assistance surface. +- **Host-specific DOM overlays.** Rejected because DOM offsets are not a supported document contract, become stale after transactions, fragment keyboard/screen-reader behavior, and force each host to rebuild navigation, application, conflict handling, and undo semantics. +- **A deterministic keyword or regex checker inside Inkspan.** Rejected because lexical triggers are not evidence of grammar, intent, tone, pragmatics, or technical correctness. Deterministic code may validate shape and coordinates but may not manufacture semantic judgments. +- **Inkspan invokes an LLM directly.** Rejected because the editor would acquire provider, credential, network, privacy, retention, availability, routing, and tenant-policy responsibilities that belong to the host. +- **The host sends whole-document rewrites.** Rejected because replacement obscures individual reasons, weakens author control, destroys revision-local evidence, and makes unintended meaning changes harder to detect. +- **Preserve diagnostics by mapping ranges across edits.** Rejected for v1 because structural position continuity does not prove that a host semantic judgment remains current after content changes. +- **Host-owned judgments through a generic Inkspan diagnostic contract.** Selected because it preserves Inkspan's provider-neutral deterministic core while giving every host one revision-safe, accessible presentation and action surface. ## Decision -Inkspan will expose a generic, additive writing-diagnostic presentation and application contract. The host supplies already-produced diagnostic proposals. Inkspan validates, anchors, renders, navigates, applies, ignores, and reports actions on those proposals, but does not decide whether the prose is correct or appropriate. +Inkspan will expose an additive writing-diagnostic contract. The host supplies already-produced proposals. Inkspan validates, revision-binds, resolves, renders, navigates, applies one selected replacement, ignores, dismisses, requests an explanation, and reports privacy-minimized action evidence. Inkspan does not decide whether prose is correct or appropriate. -A diagnostic must include, at minimum: +A v1 diagnostic includes: -- a bounded opaque `diagnostic_id`; -- the exact Inkspan strong document revision used by the host's review operation; -- the declared text-projection identity and version; -- a revision-scoped W3C `TextPositionSelector` with inclusive `start` and exclusive `end` Unicode-code-point offsets; -- a bounded host-defined `category_code` and display-safe title; -- an explanation; -- an optional proposed replacement; -- an optional bounded confidence value; -- provider/workflow provenance identifiers that contain no source text or credential; -- a host policy or judge-policy version identifying how the proposal was admitted. +- a bounded opaque `diagnosticId`; +- an exact `CwlEditorDocumentRevision` for the reviewed immutable snapshot; +- an exact `CwlEditorTextProjectionIdentity`; +- a W3C `TextPositionSelector` using inclusive `start` and exclusive `end` Unicode-code-point offsets; +- an opaque bounded `categoryCode`; +- one of `advisory`, `important`, or `critical` presentation priority; +- a plain-text title and explanation; +- an optional plain-text replacement; +- optional finite confidence in `[0, 1]`; and +- privacy-minimized workflow/judge-policy provenance identifiers with no source text, credential, or raw model output. -Inkspan may perform only deterministic validation and document operations: +Selector values are non-negative safe integers with `start <= end`. Collapsed selectors are valid evidence and remain navigable, but create no inline range decoration. -- schema, type, enum, length, count, duplicate-id, and resource-bound validation; -- projection-version and strong-revision equality checks; +Inkspan performs only deterministic validation and editor operations: + +- exact schema/type/enum/length/count/duplicate/resource validation; +- rejection of accessors, prototypes, symbols, extra fields, sparse arrays, and hostile reflection; +- projection-version and strong-revision checks; - Unicode-code-point and grapheme-boundary checks; -- selector range checks against the exact projected text; -- safe text/markup handling under the existing editor security policy; -- decoration mapping through local ProseMirror transactions when that mapping remains valid; -- stale-result invalidation when the declared revision or projection no longer matches; -- overlap/conflict detection between proposed replacements; -- one-action-at-a-time replacement, ordinary editor undo, and action callbacks. +- exact selector resolution against one immutable projected snapshot; +- safe plain-text rendering and replacement handling; +- stale-generation rejection; +- one-action-at-a-time replacement, ordinary undo, and privacy-minimized action callbacks. + +Every local or collaborative transaction with docChanged === true invalidates the complete active diagnostic generation. -Inkspan must not infer a diagnostic from keywords, regexes, phrase lists, sender domains, recipient counts, language names, or word position. Such mechanisms may validate identifiers or transport contracts, but they may not be used as a semantic fallback. If the host's model path is unavailable or returns no admitted diagnostics, Inkspan displays no fabricated judgment. +No diagnostic is preserved, mapped, remapped, repaired, or re-admitted through ProseMirror mapping, Yjs relative positions, nearest-text search, quote search, keyword search, or semantic guessing. A host that wants current guidance after an edit supplies a new set bound to a newly derived exact revision. -Applying a diagnostic is an explicit author action. A replacement is applied only if the current document still matches the diagnostic's expected revision or if Inkspan can prove a valid transaction-local mapping under the published lifecycle contract. A stale or ambiguous diagnostic never mutates the document. It returns a typed conflict or invalidation result so the host can request a fresh review. +Version 1 applies exactly one explicitly selected diagnostic at a time. Immediately before mutation, Inkspan derives and compares the exact current revision again. A stale, missing, invalid, or ambiguous proposal never mutates the document. Successful application uses one ordinary ProseMirror transaction, normal undo history, a resulting revision derived from the post-transaction document, and complete invalidation of the remaining generation. -Diagnostics are advisory. Inkspan does not block form submission, email sending, persistence, or export merely because diagnostics remain. A host may implement a separate product policy, but that policy is outside the editor package and cannot be inferred from Inkspan diagnostic priority or confidence. +Inkspan does not derive aria-invalid or any other semantic accessibility state from opaque host strings. Inline decorations contain static Inkspan classes, priority styling, and an opaque identifier only. Category, priority, title, explanation, and actions are available as text and native controls in the named diagnostics panel. + +Diagnostics are advisory. Inkspan does not block form submission, sending, persistence, export, or collaboration merely because diagnostics remain. A host may implement separate product policy outside the editor package. ## Ownership boundary Inkspan owns: -- document state and serialization; -- revision and selector evidence; -- diagnostic schema validation; -- decorations and accessible suggestion navigation; -- explicit apply/ignore/dismiss actions; -- stale-result and overlap conflict handling; -- ordinary document undo and focus restoration; -- privacy-minimized action callbacks. +- deterministic document state and serialization; +- revision/projection/selector validation; +- strict diagnostic schema and resource validation; +- semantic-neutral decorations and accessible diagnostic navigation; +- explicit Focus, Apply, Ignore, Dismiss, and Explain actions; +- strict stale-generation invalidation; +- ordinary document transactions, undo, and predictable focus behavior; +- privacy-minimized action events and redacted errors. The host owns: -- model selection and orchestration; -- prompts, rubrics, examples, and language policy; -- source email, thread, recipient, role, and organization context; +- model selection, orchestration, prompts, rubrics, examples, and language policy; +- source email/thread/recipient/role/organization context; - semantic categories, confidence calibration, acceptance policy, and abstention; - provider credentials and data-processing approval; -- diagnostic persistence and retention; -- feedback collection, evaluation, monitoring, and human escalation; -- any send, save, or compliance gate. +- diagnostic persistence, retention, evaluation, monitoring, and escalation; +- any send, save, compliance, or submission gate. ## Consequences -A host can provide inline writing guidance without forking the editor. Inkspan remains usable offline and when every model provider is unavailable. The public contract becomes broader and therefore requires packed-package, standalone, collaborative, SSR, accessibility, and cross-engine evidence. Hosts must operate a real review service and cannot treat the editor as an evaluator. +A host can provide inline writing guidance without forking the editor. Inkspan remains usable offline and when every model provider is unavailable. The public contract becomes broader and requires packed-package, standalone, collaborative, SSR, accessibility, concurrency, hostile-input, and cross-engine evidence. + +The design separates semantic authority from deterministic integrity. A host model may be wrong about prose; Inkspan can still prove that the proposal was admitted for one exact snapshot, was not moved onto changed content, and was not applied after the document changed. -The decision intentionally separates semantic authority from deterministic integrity. A model or calibrated judge may be wrong about the prose; Inkspan can still guarantee that the proposal was not silently moved to an unrelated span or applied to a different revision. +Strict invalidation may require more host refreshes, including after unrelated edits. This cost is accepted because v1 cannot prove semantic continuity through structural mapping. ## Failure and recovery -- Missing, malformed, oversized, duplicate, unsupported-projection, or out-of-range diagnostics are rejected without document mutation. -- A provider timeout, quota error, malformed model result, or host policy abstention is represented by absence or a host-owned status outside the diagnostic list. Inkspan authoring remains available. -- A changed document invalidates stale diagnostics. Inkspan never “repairs” them by searching for keywords or selecting the nearest matching sentence. -- A safely mapped local transaction may preserve a diagnostic only when the mapping contract proves the selected range still denotes the intended content. Ambiguity invalidates it. -- Overlapping replacements are applied separately and revalidated after every mutation. “Apply all” is permitted only for an explicitly validated non-overlapping batch under one current revision. -- A collaborative remote edit follows the same invalidation rule; raw local positions are not durable Yjs anchors. +- Missing, malformed, oversized, duplicate, unsupported-projection, or invalid-selector input is rejected without document mutation. +- Provider timeout, quota failure, malformed model result, or policy abstention remains host-owned absence/status; Inkspan authoring stays available. +- Any local or remote document change invalidates the complete generation before further action authority exists. +- Async digest/selector work from an invalidated generation is discarded. +- Apply/Ignore/Dismiss/Explain against a missing or stale generation returns typed non-mutating evidence. +- Host callback exceptions are contained and cannot corrupt editor state. +- Recovery is a newly supplied diagnostic set for the current exact revision. Inkspan never fabricates a fallback judgment. ## Security and privacy impact -Diagnostics and replacements are untrusted host-controlled input. They pass through the same safe-link, inline-image, clipboard, schema, and transaction boundaries as other editor input. A diagnostic must not contain model credentials, raw provider request/response bodies, tenant identifiers not needed by the component, or hidden executable markup. +Diagnostics and replacements are untrusted host-controlled input. The exact data-property boundary rejects executable or ambiguous object shapes. Host strings render as text; v1 accepts no HTML, JavaScript, command, arbitrary TipTap JSON, arbitrary transaction, or embedded host callback. -The public diagnostic contract does not require copying the selected source text. Position selectors remain revision-scoped and privacy-minimized. Hosts that add quote selectors, store explanations, or transmit the document to a model own authorization, encryption, provider data-use terms, retention, audit, and regional-processing controls. +The contract does not require copying selected source text. Revision and selector evidence is privacy-minimized. Hosts transmitting documents to a model own authorization, encryption, provider data-use terms, retention, audit, regional processing, and consent. -Generic telemetry may record bounded category, action, conflict reason, latency bucket, and policy version. It must not record authored source text, suggested replacement text, full explanations, prompts, raw model output, or document envelopes by default. +Default action events may contain only opaque identifier, exact revision evidence, opaque category, generation, action, and bounded reason code. They do not contain authored source text, replacement text, explanation, prompt, raw model output, document envelope, credential, email recipient, or tenant identifier. + +Opaque values that happen to contain words such as `spelling`, `grammar`, `mechanics`, `rude`, `incorrect`, or multilingual equivalents gain no semantic behavior or ARIA authority. ## Accessibility -Diagnostics must be available without relying on color or hover. The eventual implementation must provide: +Diagnostics are available without relying on color, hover, pointer input, animation, or generated CSS content. The built-in panel provides: -- a keyboard-reachable diagnostics summary; -- previous/next diagnostic navigation; -- an accessible name for category and affected passage; -- predictable focus movement between editor range and suggestion card; -- explicit Apply, Ignore, Dismiss, and Explain actions; -- polite status announcements after application or invalidation; -- no focus theft while new asynchronous diagnostics arrive; -- equivalent behavior in standalone and collaborative editors. +- a named region, count, and ordered list; +- category, priority, title, and explanation as text; +- previous/next navigation and explicit affected-range focus; +- native Apply, Ignore, Dismiss, and Explain actions; +- disabled Apply when no replacement exists; +- polite status after ordinary actions; +- an assertive alert only for an actual application conflict; +- stable focus when async diagnostics arrive; and +- standalone/collaborative parity. -Underlines are a visual supplement, not the sole information channel. +Underlines are a visual supplement, not the semantic accessibility surface. ## Compatibility and migration -The feature is additive. Existing hosts that do not pass diagnostics retain identical editor, serialization, form, persistence, and collaboration behavior. The diagnostic contract must remain optional and provider-neutral. It may be released only in a version whose package declarations, ESM/CJS outputs, React peer ranges, styles, and consumer verification expose the same contract. +The feature is additive. Hosts that omit diagnostics retain identical editor, serialization, form, persistence, export, and collaboration behavior. The diagnostic contract remains optional and provider-neutral. + +No canonical document-envelope or database migration is required because diagnostics are noncanonical review state. A future explicit semantic accessibility field or cross-edit continuity model requires a new versioned type, compatibility plan, and ADR; v1 opaque fields gain no retrospective meaning. -No canonical document-envelope schema change is required. Diagnostics are review state, not canonical document content. Rollback removes the diagnostic props, decorations, and action surface without document migration. +The feature may be released only when root and framework-neutral subpaths, ESM/CommonJS/types/CSS, React peer compatibility, package consumers, SSR, browser evidence, and rollback contracts agree. ## Verification Acceptance requires tests proving: -- strict resource-bounded schema validation and duplicate rejection; -- Unicode astral characters, Korean/CJK text, combining marks, emoji, bidirectional text, and grapheme-boundary behavior; -- exact revision/projection binding; -- stale-result rejection without mutation; -- transaction mapping only when meaning-preserving range identity is provable; -- overlapping replacement conflict behavior; -- single and bounded batch application plus undo; -- safe rendering of hostile titles, explanations, and replacements; -- keyboard, focus, live-region, and screen-reader semantics; -- standalone/collaborative parity and remote-edit invalidation; -- SSR-safe initial shell and hydration; -- packed ESM/CommonJS/types/CSS consumer compatibility; -- no model SDK, credential, environment, database, or network dependency in the Inkspan package; -- no source text or replacement text in default telemetry; -- exact 100% production statement, branch, function, and line coverage and complete public API documentation. - -Contract tests must also prohibit semantic keyword fallback: adversarial fixtures with identical keywords but different meanings, and paraphrases with different words but the same issue, must prove that Inkspan itself produces no judgment. It renders only host-supplied diagnostics. +- strict exact-field/resource validation, duplicate rejection, and hostile-reflection containment; +- Unicode astral, Korean/CJK, combining, emoji, bidirectional, grapheme, empty, and collapsed selector behavior; +- exact revision/projection binding and one-snapshot selector resolution; +- local and Yjs remote `docChanged` invalidation; +- no transaction mapping, nearest-text, quote, keyword, or semantic repair; +- no semantic ARIA derivation from opaque host fields; +- one selected replacement only, exact pre-mutation recheck, ordinary undo, and resulting revision; +- safe text rendering of hostile titles, explanations, categories, identifiers, and replacements; +- keyboard/focus/status/alert/non-color accessibility; +- standalone/collaborative parity and no awareness publication; +- SSR/hydration and packed ESM/CommonJS/types/CSS compatibility; +- no model SDK, credential, environment, database, filesystem, or network dependency in the package; +- no authored/model text in default action evidence or telemetry; +- exact 100% owned production statement, branch, function, and line coverage plus complete public documentation. + +Contrast fixtures with identical keywords but different meanings, and paraphrases with different words but the same issue, must prove that Inkspan produces zero diagnostics unless the host supplies them. ## Research and standards traceability -This decision uses the W3C Web Annotation Data Model's Unicode-code-point `TextPositionSelector` semantics together with Inkspan's stronger revision binding. It treats LLM judgments as fallible measurement outputs rather than deterministic truth, consistent with published findings on position, verbosity, self-preference, artifact, multilingual, and consistency biases in LLM evaluators. The accompanying design and doctoring records contain APA 7th citations and the host-side calibration implications. +The decision uses W3C Web Annotation Data Model Unicode-code-point `TextPositionSelector` semantics together with stronger exact revision binding. It uses ProseMirror/TipTap immutable state and transaction contracts, RFC 9110 strong entity-tag semantics, and the existing Inkspan deterministic-versus-model-assisted authoring boundary. LLM judgments remain fallible host evidence rather than editor truth; the companion doctoring record contains APA 7 references and calibration implications. ## Rollback or supersession -Rollback removes the optional diagnostic surface while preserving canonical documents, revision evidence, selection evidence, and deterministic authoring. Supersession requires a new ADR if Inkspan is ever proposed to own model invocation, semantic classification, persistence authority, or submission policy. Such a change must provide explicit provider neutrality, privacy, offline/degraded operation, accessibility, compatibility, migration, and rollback evidence. \ No newline at end of file +Rollback removes optional diagnostic props, controller state, decorations, panel, styles, and action APIs while preserving canonical documents, revisions, persistence, and collaboration data. + +Supersession requires a new ADR if Inkspan is proposed to own model invocation, semantic classification, persistence authority, submission policy, semantic ARIA inference, cross-edit diagnostic continuity, or batch application. Such a change must provide provider neutrality, privacy, offline/degraded operation, accessibility, compatibility, migration, recovery, and rollback evidence. From f6f20e47ebc3cffc90960f9b2adfac761deb976c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 20:48:24 +0900 Subject: [PATCH 15/18] docs(plan): reconcile writing diagnostics v1 tasks --- ...8-12-writing-diagnostics-implementation.md | 532 ++++-------------- 1 file changed, 118 insertions(+), 414 deletions(-) diff --git a/docs/superpowers/plans/2026-08-12-writing-diagnostics-implementation.md b/docs/superpowers/plans/2026-08-12-writing-diagnostics-implementation.md index 27517d31..15b686e8 100644 --- a/docs/superpowers/plans/2026-08-12-writing-diagnostics-implementation.md +++ b/docs/superpowers/plans/2026-08-12-writing-diagnostics-implementation.md @@ -1,458 +1,162 @@ # Revision-Bound Writing Diagnostics Implementation Plan -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Add a generic, provider-neutral Inkspan surface that validates, displays, navigates, applies, ignores, dismisses, and invalidates host-supplied writing diagnostics without making any semantic judgment itself. - -**Architecture:** A React-free `writing-diagnostics` contract validates bounded host input and resolves revision-scoped W3C text selectors against Inkspan's canonical text projection. A ProseMirror extension renders verified ranges and clears them on every document-changing transaction. A shared React controller binds asynchronous revision verification to one editor generation, while a built-in accessible panel exposes explicit actions. Standalone and collaborative editors reuse the same contract, controller, extension, and action result types. Version 1 accepts plain-text replacements only and never searches for similar text after a revision mismatch. - -**Tech Stack:** TypeScript, React 18/19, TipTap/ProseMirror, Yjs collaboration, Web Crypto revision evidence, W3C `TextPositionSelector`, Vitest with jsdom, Playwright across Chromium/Firefox/WebKit, Vite package subpath builds, pnpm, and the existing exact-head coverage/package/release gates. - -## Global Constraints - -- Inkspan does not call an LLM, provider, network service, database, storage service, or host API. -- Inkspan never infers grammar, spelling, tone, clarity, pragmatics, technical quality, or actionability from text. -- Keywords, regular expressions, phrase dictionaries, sender domains, language names, recipient counts, nearest-text search, and word positions are prohibited as semantic fallback or stale-selector repair. -- Version 1 replacements are plain text. HTML, commands, editor JSON, JavaScript, and arbitrary ProseMirror transactions are not accepted from a diagnostic. -- The canonical selector projection remains `inkspan-prosemirror-text` version `1`; a new projection requires a separate ADR and compatibility plan. -- Any document-changing local or collaborative transaction invalidates every active diagnostic before it can be applied. Version 1 does not retain or remap a diagnostic across a changed document. -- A diagnostic is actionable only after its declared strong revision, projection identity, selector, and grapheme boundaries have been verified against one exact editor snapshot. -- Asynchronous revision checks use generation tokens and never publish results for a replaced editor, destroyed editor, newer diagnostic set, or changed document. -- Default hard limits: - - 256 diagnostics per editor snapshot; - - 256 characters per `diagnosticId`; - - 128 characters per `categoryCode` and provenance identifier; - - 256 characters per title; - - 4,000 characters per explanation; - - 20,000 characters per replacement; - - confidence in the closed interval `[0, 1]`. -- Action callbacks and default telemetry-safe result objects contain opaque identifiers, revisions, category, action, bounded reason codes, and timing state only. They do not contain selected source text, replacement text, explanation, prompt, model output, document envelope, credential, or tenant identifier. -- Diagnostics remain advisory. Inkspan does not block form submission, persistence, export, or sending. -- Existing editor behavior is byte-for-byte and interaction-compatible when `writingDiagnostics` is absent. -- Production statement, branch, function, and line coverage remains exactly 100%. -- Public APIs and every shipped module/class/function receive beginner-readable documentation. -- Feature work remains under `Unreleased`; a separate exact-head release-only PR publishes the next compatible minor version after all acceptance gates pass. - ---- - -## Task 1: Define the React-Free Diagnostic Contract - -**Files:** -- Create: `src/writingDiagnostics.ts` -- Create: `src/writingDiagnostics.test.ts` -- Create: `src/writing-diagnostics/index.ts` -- Modify: `src/index.ts` - -- [ ] Write failing tests for valid diagnostics, empty arrays, duplicate IDs, unexpected fields, inherited fields, accessors, symbols, proxy exceptions, oversized arrays/strings, invalid confidence, unsupported priority, malformed revision, unsupported projection, invalid selector order, and non-string replacement values. -- [ ] Define the public v1 types: - -```ts -export type CwlWritingDiagnosticPriority = - | 'advisory' - | 'important' - | 'critical'; - -export interface CwlWritingDiagnostic { - readonly diagnosticId: string; - readonly documentRevision: CwlEditorDocumentRevision; - readonly textProjection: CwlEditorTextProjectionIdentity; - readonly selector: CwlEditorTextPositionSelector; - readonly categoryCode: string; - readonly priority: CwlWritingDiagnosticPriority; - readonly title: string; - readonly explanation: string; - readonly suggestedReplacement?: string; - readonly confidence?: number; - readonly provenance: Readonly<{ - workflowId: string; - workflowVersion: string; - judgePolicyVersion: string; - orchestrationMode?: string; - }>; -} -``` +> **For agentic workers:** Use the repository's test-driven, exact-head, single-writer workflow. Every task begins with a realistic failing regression, implements the smallest bounded production change, removes temporary branch-only workflows before integration, and regenerates all current-head evidence after ancestry changes. -- [ ] Define stable, redacted error codes and `WritingDiagnosticError` for contract, limit, revision, projection, selector, conflict, and lifecycle failures. -- [ ] Define frozen default limits and a strict `validateWritingDiagnostics(input, limits?)` function that returns a deeply detached, deeply frozen tuple. -- [ ] Validate only own data properties. Catch hostile object/proxy behavior and return a stable error without reflecting source values. -- [ ] Reject duplicate diagnostic IDs and reject any object whose exact field set differs from the v1 schema. -- [ ] Preserve diagnostic order from the host; do not sort by confidence, category, wording, or source position inside the validator. -- [ ] Export only the React-free contract, validator, constants, limits, and error types from `src/writing-diagnostics/index.ts`. -- [ ] Re-export the same contract from the root package for interactive consumers. -- [ ] Run: - -```bash -pnpm exec vitest run src/writingDiagnostics.test.ts -pnpm typecheck -``` +**Goal:** Add a generic, provider-neutral Inkspan surface that validates, displays, navigates, applies, ignores, dismisses, explains, and invalidates host-supplied writing diagnostics without making semantic judgments itself. -- [ ] Commit: +**Architecture:** A React-free contract validates bounded hostile input. A deterministic inverse text-projection resolver maps revision-scoped W3C selectors to one immutable ProseMirror snapshot. A semantic-neutral extension renders verified ranges. One shared React controller fences async verification by generation and invalidates the complete set after every document change. A built-in accessible panel exposes explicit actions. Standalone and collaborative editors reuse the same types and lifecycle. Version 1 accepts plain-text replacements, applies one explicitly selected diagnostic at a time, and never repairs stale selectors. -```bash -git add src/writingDiagnostics.ts src/writingDiagnostics.test.ts src/writing-diagnostics/index.ts src/index.ts -git commit -m "feat(diagnostics): add strict writing diagnostic contract" -``` +**Technology:** TypeScript, React 18/19, TipTap/ProseMirror, Yjs, Web Crypto revision evidence, W3C `TextPositionSelector`, Vitest/jsdom, Playwright Chromium/Firefox/WebKit, Vite package subpaths, pnpm, and existing exact-head coverage/security/package/release gates. -## Task 2: Add an Inverse Canonical Text-Projection Resolver - -**Files:** -- Create: `src/writingDiagnosticProjection.ts` -- Create: `src/writingDiagnosticProjection.test.ts` -- Modify: `src/textPositionSelectorEvidence.ts` -- Modify: `src/text-position-selector/index.ts` - -- [ ] Write failing tests that resolve selectors over paragraphs, headings, lists, tables, hard breaks, inline text, non-text leaf nodes, astral characters, Korean/CJK text, combining marks, emoji sequences, bidirectional text, empty blocks, and document boundaries. -- [ ] Add negative tests for unsupported projection versions, negative/non-integral offsets, reversed ranges, out-of-range offsets, grapheme-splitting boundaries, ambiguous projection boundaries, and runtimes without `Intl.Segmenter`. -- [ ] Implement a single-pass `buildTextProjectionMap(documentNode)` that emits the exact same text as `documentNode.textBetween(0, documentNode.content.size, '\n', '\uFFFC')` plus a boundary map between Unicode-code-point offsets and ProseMirror positions. -- [ ] Assert in tests that the independently built projection is exactly equal to the existing canonical projection for every fixture and generated document. -- [ ] Add: - -```ts -export function resolveTextPositionSelector( - documentNode: ProseMirrorNode, - selector: CwlEditorTextPositionSelector, - textProjection: CwlEditorTextProjectionIdentity, -): Readonly<{ from: number; to: number }>; -``` +## Global constraints -- [ ] Fail closed when a code-point boundary cannot map to one unambiguous ProseMirror position; never pick the nearest sentence, matching word, or repeated substring. -- [ ] Reuse one shared grapheme-boundary implementation for selector creation and resolution so forward and inverse paths cannot diverge. -- [ ] Add property tests that create a valid structural selection, convert it with `createTextPositionSelector()`, resolve it back, and recover the exact original range. -- [ ] Run: +- Inkspan calls no model, provider, network service, database, storage service, credential broker, or host API. +- Inkspan never infers spelling, grammar, tone, clarity, pragmatics, technical quality, or actionability from text or opaque host fields. +- Keywords, regexes, phrase dictionaries, language names, domains, recipient counts, nearest-text search, quote search, and word positions are prohibited as semantic fallback or stale-selector repair. +- Host strings are untrusted plain text. Version 1 accepts no diagnostic HTML, command, JavaScript, arbitrary TipTap JSON, arbitrary transaction, or callback. +- `inkspan-prosemirror-text` version 1 is the sole v1 selector projection. +- Selector offsets are non-negative safe integers with `start <= end`; collapsed selectors remain navigable but create no inline range decoration. +- Every local or collaborative `docChanged` transaction invalidates the complete active generation. Version 1 never maps or preserves a diagnostic across changed content. +- One immutable editor snapshot supplies the current revision and every selector resolution for one generation. +- Generation, mounted-state, and editor-identity guards prevent stale promises from installing decorations, emitting actions, or moving focus. +- Diagnostics remain advisory and never block form submission, sending, persistence, export, or collaboration. +- Existing behavior remains compatible when diagnostics are absent. +- Owned production statement, branch, function, and line coverage remains exactly 100%, with beginner-readable public documentation. +- The feature stays under `Unreleased`; publication uses a separate release-only PR. -```bash -pnpm exec vitest run src/textPositionSelectorEvidence.test.ts src/writingDiagnosticProjection.test.ts -pnpm typecheck -``` +## Task 1: React-free diagnostic contract -- [ ] Commit: +**Files:** `src/writingDiagnostics.ts`, `src/writingDiagnostics.test.ts`, `src/writing-diagnostics/index.ts`, `src/index.ts`. -```bash -git add src/textPositionSelectorEvidence.ts src/text-position-selector/index.ts src/writingDiagnosticProjection.ts src/writingDiagnosticProjection.test.ts -git commit -m "feat(diagnostics): resolve revision-scoped text selectors" -``` +- [ ] Add RED tests for valid/empty sets, exact fields, duplicate IDs, hostile arrays/objects/proxies/accessors/prototypes/symbols, resource ceilings, priorities, confidence, revision, projection, selectors, provenance, and replacement types. +- [ ] Define `advisory | important | critical`, structured `CwlEditorDocumentRevision`, structured projection identity, selector, opaque category, bounded host prose, optional plain-text replacement/confidence, and privacy-minimized provenance. +- [ ] Define stable redacted error codes for contract, limit, revision, projection, selector, conflict, and lifecycle failures. +- [ ] Validate exact own enumerable data properties and return deeply detached frozen values. +- [ ] Preserve host order; do not sort or infer semantics. +- [ ] Export identical root and React-free subpath contracts. +- [ ] Run focused tests and typecheck to GREEN. -## Task 3: Build the ProseMirror Decoration and Invalidation Extension +## Task 2: inverse canonical text-projection resolver -**Files:** -- Create: `src/extensions/WritingDiagnostics.ts` -- Create: `src/extensions/WritingDiagnostics.test.ts` -- Modify: `src/extensions/kit.ts` +**Files:** `src/writingDiagnosticProjection.ts`, its tests, `src/textPositionSelectorEvidence.ts`, `src/text-position-selector/index.ts`. -- [ ] Write failing extension-state tests for installing a verified diagnostic set, inline decoration attributes, collapsed/empty ranges, duplicate install generations, clear commands, editor destruction, and document-changing transactions. -- [ ] Define one plugin key and typed transaction metadata for `install`, `focus`, and `clear` operations. -- [ ] Render verified non-empty ranges with safe static attributes only: +- [ ] Add RED round-trip tests for paragraphs, headings, lists, tables, hard breaks, inline/block atoms, empty blocks, document boundaries, repeated text, astral/Korean/CJK/combining/emoji/bidirectional text, and collapsed ranges. +- [ ] Add negative tests for unsupported projection, unsafe/reversed/out-of-range offsets, grapheme splits, ambiguous structural boundaries, hostile metadata, and missing `Intl.Segmenter`. +- [ ] Build the exact `textBetween(0, size, '\n', '\uFFFC')` projection and one code-point-boundary-to-ProseMirror-position map. +- [ ] Reuse one grapheme-boundary implementation for forward and inverse paths. +- [ ] Fail closed rather than selecting nearest or similar text. +- [ ] Publish the deterministic resolver through the framework-neutral selector surface. -```text -class="cwl-writing-diagnostic cwl-writing-diagnostic--{priority}" -data-cwl-diagnostic-id="opaque-id" -aria-invalid="spelling" only when the host category explicitly maps to mechanics -``` +## Task 3: semantic-neutral ProseMirror decorations -- [ ] Do not inject title, explanation, replacement, category text, model output, or HTML into decoration attributes. -- [ ] Clear all decorations and focused-diagnostic state on every `transaction.docChanged`, including Yjs remote transactions. -- [ ] Do not map a diagnostic through a changed document in version 1. -- [ ] Expose typed helper commands that accept already-validated, already-resolved ranges; the extension itself must not hash documents or call host code. -- [ ] Add the extension exactly once through `buildExtensions()` for standalone and collaborative editor graphs. -- [ ] Prove by source and runtime tests that an editor with no diagnostics has no added visual output, action surface, or document mutation. -- [ ] Run: - -```bash -pnpm exec vitest run src/extensions/WritingDiagnostics.test.ts src/extensions/kit.test.ts -pnpm typecheck -``` +**Files:** `src/extensions/WritingDiagnostics.ts`, its tests, `src/extensions/kit.ts`. -- [ ] Commit: +- [ ] Add RED tests for install/focus/clear metadata, monotonic generation, exact ranges, collapsed ranges, malformed metadata, duplicate IDs, resource ceilings, editor destruction, and local/remote document changes. +- [ ] Render non-empty ranges with only: -```bash -git add src/extensions/WritingDiagnostics.ts src/extensions/WritingDiagnostics.test.ts src/extensions/kit.ts -git commit -m "feat(diagnostics): add fail-closed editor decorations" +```text +class="cwl-writing-diagnostic cwl-writing-diagnostic--{priority}" +data-cwl-diagnostic-id="opaque-id" ``` -## Task 4: Implement the Revision-Bound Controller State Machine +- [ ] Reject all extra semantic fields and never derive spelling/grammar validity or other semantic ARIA state from host strings. +- [ ] Clear the complete generation before processing metadata on every `docChanged` transaction. +- [ ] Accept only already-validated structural ranges; perform no hashing or host callback. +- [ ] Install the extension exactly once for standalone and collaborative editor graphs. -**Files:** -- Create: `src/components/useWritingDiagnosticsController.ts` -- Create: `src/components/useWritingDiagnosticsController.test.tsx` -- Modify: `src/types.ts` +## Task 4: revision-bound controller -- [ ] Write failing hook tests for initial verification, replacement diagnostic props, same-array identity with mutated hostile members, editor replacement, editor destruction, revision mismatch, projection mismatch, verification rejection, document change during hashing, overlapping asynchronous requests, and callback replacement without editor recreation. -- [ ] Define controller states: - -```text -absent -> verifying -> active - -> invalid -active -> applying -> applied -active -> ignored | dismissed | explanation_requested -active -> stale on any document change -``` +**Files:** `src/components/useWritingDiagnosticsController.ts`, its tests, `src/types.ts`. -- [ ] Add public action/result types with stable reason codes and no authored text: +- [ ] Add RED tests for absent/invalid/verifying/active/stale states, editor and callback replacement, hostile prop mutation, revision/projection mismatch, selector rejection, doc changes during hashing, concurrent promises, unmount, and callback exceptions. +- [ ] Validate the complete set before reading editor state. +- [ ] Capture one immutable envelope, derive one revision, resolve all selectors against that same snapshot, and install atomically. +- [ ] Fence every async continuation with monotonic generation, mounted state, and editor identity. +- [ ] Invalidate both verifying and active generations immediately after any local/remote document change; do not map ranges. +- [ ] Expose privacy-minimized Focus/Ignore/Dismiss/Explain controller actions and typed reason codes. -```ts -export type CwlWritingDiagnosticAction = - | 'applied' - | 'ignored' - | 'dismissed' - | 'requested_explanation' - | 'stale' - | 'conflict'; -``` +## Task 5: accessible diagnostics panel -- [ ] Validate diagnostics before reading the editor. -- [ ] Capture one immutable document envelope and derive one strong revision from that same snapshot before resolving any selector. -- [ ] Compare the declared revision and projection exactly; no normalization or compatibility guess is permitted. -- [ ] Resolve all ranges against the same immutable snapshot and reject the complete set atomically if structural validation fails. -- [ ] Treat overlapping diagnostics as displayable but prevent overlapping replacements from being batch-applied. Version 1 exposes single application only. -- [ ] Subscribe to editor transactions and immediately invalidate the active generation before scheduling any new verification. -- [ ] Use monotonic generation IDs and mounted/editor identity guards so older promises cannot install decorations, emit actions, or change focus. -- [ ] Contain host callback exceptions and keep editor state deterministic. -- [ ] Run: - -```bash -pnpm exec vitest run src/components/useWritingDiagnosticsController.test.tsx -pnpm typecheck -``` +**Files:** `src/components/WritingDiagnosticsPanel.tsx`, its tests, `src/components/EditorFrame.tsx`, `src/styles.css`, print-style tests. -- [ ] Commit: +- [ ] Add RED accessibility tests for a named region, count, ordered items, category, priority, title, explanation, affected-range focus, previous/next navigation, Apply/Ignore/Dismiss/Explain, disabled Apply without replacement, polite status, and assertive conflict alert. +- [ ] Prove asynchronous arrival does not steal focus and explicit navigation uses roving focus. +- [ ] Render every host string as a React text node; never use raw HTML. +- [ ] Keep selected source text out of action names and attributes. +- [ ] Add forced-colors, high-contrast, reduced-motion, focus-visible, touch-target, zoom, and print behavior. Default print remains document-only; an explicit host option may include a bounded appendix. +- [ ] Add no undocumented global shortcut. -```bash -git add src/components/useWritingDiagnosticsController.ts src/components/useWritingDiagnosticsController.test.tsx src/types.ts -git commit -m "feat(diagnostics): bind diagnostics to exact editor revisions" -``` +## Task 6: standalone editor actions -## Task 5: Add Accessible Diagnostic Navigation and Action UI - -**Files:** -- Create: `src/components/WritingDiagnosticsPanel.tsx` -- Create: `src/components/WritingDiagnosticsPanel.test.tsx` -- Modify: `src/components/EditorFrame.tsx` -- Modify: `src/styles.css` - -- [ ] Write failing accessibility tests for a named region, count summary, ordered diagnostic list, category/priority/title/explanation, affected-range focus, previous/next navigation, Apply/Ignore/Dismiss/Explain actions, live status, and disabled application when no replacement exists. -- [ ] Add tests proving that asynchronous diagnostic arrival does not move focus and that explicit navigation returns focus predictably between the editor range and panel card. -- [ ] Add tests proving information remains available without color, hover, pointer input, animation, or generated CSS content. -- [ ] Render host strings as React text nodes only. Never use `dangerouslySetInnerHTML`. -- [ ] Give every action an explicit accessible name that includes the diagnostic title but does not copy the selected source passage into an attribute. -- [ ] Use buttons for previous/next navigation and roving focus within the list; do not add undocumented global shortcuts in version 1. -- [ ] Add one polite live region for completed actions and one assertive alert only for an application conflict. -- [ ] Add priority-specific underline styles plus forced-colors, high-contrast, reduced-motion, print, touch-target, and focus-visible rules. -- [ ] In print, omit action buttons and include a compact diagnostic appendix only when the host explicitly enables `printWritingDiagnostics`; default print output remains document-only. -- [ ] Add `writingDiagnosticsPanel?: ReactNode` support to `EditorFrame` only as an internally constructed trusted component slot; hosts do not inject raw diagnostic markup. -- [ ] Run: - -```bash -pnpm exec vitest run src/components/WritingDiagnosticsPanel.test.tsx src/components/EditorFrame.test.tsx src/printStyles.test.ts -pnpm typecheck -``` +**Files:** `src/types.ts`, `src/components/CwlEditor.tsx`, `src/components/useEditorHandle.ts`, integration/accessibility/handle tests. -- [ ] Commit: +- [ ] Add RED tests for optional props, hostile/valid/stale sets, explicit actions, clear, read-only/disabled modes, form submission, undo, callback replacement, and unmount. +- [ ] Preserve the raw diagnostics prop by identity until bounded controller validation. +- [ ] Add additive props for diagnostics, action/error callbacks, label, and optional print appendix. +- [ ] Add imperative Focus/Ignore/Dismiss/Explain/Apply methods through the same controller used by the panel. +- [ ] Immediately before Apply, derive and compare the exact current revision again. +- [ ] Apply one plain-text replacement through one ordinary ProseMirror transaction, derive the resulting revision, emit the complete event, and invalidate the remaining generation. +- [ ] Return typed non-mutating outcomes for stale/conflict/lifecycle cases. +- [ ] Prove source words alone generate no diagnostics. -```bash -git add src/components/WritingDiagnosticsPanel.tsx src/components/WritingDiagnosticsPanel.test.tsx src/components/EditorFrame.tsx src/styles.css -git commit -m "feat(diagnostics): add accessible writing guidance UI" -``` +## Task 7: collaborative parity -## Task 6: Integrate Standalone Editor Props and Imperative Actions - -**Files:** -- Modify: `src/types.ts` -- Modify: `src/components/CwlEditor.tsx` -- Modify: `src/components/useEditorHandle.ts` -- Modify: `src/components/CwlEditor.test.tsx` -- Modify: `src/components/CwlEditor.accessibility.test.tsx` -- Modify: `src/components/useEditorHandle.test.tsx` -- Create: `src/components/CwlEditor.writingDiagnostics.test.tsx` - -- [ ] Write failing integration tests for omitted props, valid diagnostics, invalid diagnostics, stale revisions, editor updates, Apply/Ignore/Dismiss/Explain, clear, undo, host callback replacement, read-only mode, disabled editor, form submission, and unmount. -- [ ] Add optional props: - -```ts -writingDiagnostics?: readonly CwlWritingDiagnostic[]; -onWritingDiagnosticAction?: (event: CwlWritingDiagnosticActionEvent) => void; -onWritingDiagnosticsError?: (error: WritingDiagnosticError) => void; -writingDiagnosticsLabel?: string; -printWritingDiagnostics?: boolean; -``` +**Files:** collaborative editor/index/tests and a two-client diagnostics suite. -- [ ] Preserve the original diagnostics array by identity until the controller performs bounded validation; editor construction must not deeply inspect hostile values. -- [ ] Add imperative methods for focus, ignore, dismiss, explanation request, and asynchronous apply. Every method returns a typed result instead of throwing for ordinary stale/conflict outcomes. -- [ ] Apply one plain-text replacement through an ordinary ProseMirror transaction only after a second exact-current-revision check immediately before mutation. -- [ ] Compute and return the resulting strong revision from the applied post-transaction document, and emit the host callback only after the result is complete. -- [ ] Ensure the transaction enters the normal undo history and does not bypass clipboard, link, image, schema, or document-envelope policy. -- [ ] Immediately invalidate every remaining diagnostic after a successful apply. -- [ ] Keep editing, form submission, conversion, save, and export available when diagnostics are invalid, unavailable, stale, ignored, or unhandled. -- [ ] Prove a document containing words such as `rude`, `incorrect`, `urgent`, or their multilingual equivalents produces zero diagnostics unless the host supplies them. -- [ ] Run: - -```bash -pnpm exec vitest run src/components/CwlEditor.writingDiagnostics.test.tsx src/components/CwlEditor.accessibility.test.tsx src/components/useEditorHandle.test.tsx -pnpm typecheck -``` +- [ ] Add the same props/actions and accessible panel behavior through the shared controller and extension. +- [ ] Prove a remote Yjs insertion invalidates the complete local set before application. +- [ ] Prove remote change during pending hashing discards the stale continuation. +- [ ] Keep diagnostics, explanations, replacements, selected text, model provenance, and review state out of awareness payloads. +- [ ] Emit an action only from the client whose user invoked it. +- [ ] Preserve editor/provider/Yjs identity when diagnostics/callbacks change. -- [ ] Commit: +## Task 8: framework-neutral package subpath -```bash -git add src/types.ts src/components/CwlEditor.tsx src/components/useEditorHandle.ts src/components/CwlEditor.test.tsx src/components/CwlEditor.accessibility.test.tsx src/components/useEditorHandle.test.tsx src/components/CwlEditor.writingDiagnostics.test.tsx -git commit -m "feat(diagnostics): integrate revision-safe editor actions" -``` +**Files:** package manifest/lock, Vite subpath config, package/export/consumer/boundary tests. -## Task 7: Establish Collaborative Editor Parity - -**Files:** -- Modify: `src/collaboration/CollaborativeCwlEditor.tsx` -- Modify: `src/collaboration/index.ts` -- Modify: `src/collaboration/index.test.ts` -- Modify: `src/collaboration/CollaborativeCwlEditor.test.tsx` -- Modify: `src/collaboration/CollaborativeCwlEditor.accessibility.test.tsx` -- Create: `src/collaboration/CollaborativeCwlEditor.writingDiagnostics.test.tsx` - -- [ ] Write failing tests for the same public props/actions as standalone Inkspan. -- [ ] Add a two-client Yjs test proving a remote insertion invalidates the local client's complete diagnostic set before any application can occur. -- [ ] Add a race test in which remote content changes while the local revision digest is pending; the older digest must not install decorations. -- [ ] Reuse `useWritingDiagnosticsController`; do not create a second collaborative-specific semantic or lifecycle implementation. -- [ ] Ensure awareness payloads never contain diagnostics, explanations, replacements, selected text, model provenance, or review state. -- [ ] Ensure remote action callbacks are not fabricated: only the client whose user explicitly invoked an action emits that action. -- [ ] Prove editor/provider/Yjs identity remains stable when diagnostics or callbacks change. -- [ ] Run: - -```bash -pnpm exec vitest run src/collaboration/CollaborativeCwlEditor.writingDiagnostics.test.tsx src/collaboration/CollaborativeCwlEditor.accessibility.test.tsx src/collaboration/index.test.ts -pnpm typecheck -``` +- [ ] Publish `@contextualwisdomlab/cwl-editor/writing-diagnostics` as React-free ESM/CommonJS/types. +- [ ] Export contracts, limits, validation, errors, and deterministic selector-resolution primitives only. +- [ ] Prove no React, TipTap React, provider, model SDK, network, credential, filesystem, database, or CSS side-effect dependency leaks into the pure subpath. +- [ ] Install the packed tarball into isolated ESM/CommonJS/strict-TypeScript consumers. +- [ ] Keep UI and editor handles on interactive root/collaboration entrypoints. -- [ ] Commit: +## Task 9: browser, SSR, hostile-input, and no-fallback assurance -```bash -git add src/collaboration/CollaborativeCwlEditor.tsx src/collaboration/index.ts src/collaboration/index.test.ts src/collaboration/CollaborativeCwlEditor.test.tsx src/collaboration/CollaborativeCwlEditor.accessibility.test.tsx src/collaboration/CollaborativeCwlEditor.writingDiagnostics.test.tsx -git commit -m "feat(diagnostics): guarantee collaborative parity" -``` +**Files:** browser specs/fixture, security tests, server-rendering tests, `docs/TEST_STRATEGY.md`. -## Task 8: Publish a Framework-Neutral Package Subpath - -**Files:** -- Modify: `package.json` -- Modify: `pnpm-lock.yaml` -- Create: `vite.writing-diagnostics.config.ts` -- Modify: `scripts/verify-package.mjs` -- Modify: `src/packageExports.test.ts` -- Modify: `src/packageConsumer.test.ts` -- Create: `src/writing-diagnostics/packageBoundary.test.ts` - -- [ ] Add `@contextualwisdomlab/cwl-editor/writing-diagnostics` as a React-free ESM/CommonJS/type subpath. -- [ ] Keep UI components and editor handles on the root and collaboration entrypoints; the subpath exports only types, limits, validation, errors, and selector-resolution primitives that do not require React. -- [ ] Add a dedicated Vite build configuration and package export map entries. -- [ ] Add dependency-graph tests proving the subpath has no React, TipTap React, Yjs provider, model SDK, network, credential, filesystem, or database import. -- [ ] Extend package verification to install the packed tarball in isolated strict TypeScript ESM and CommonJS consumers and compile a complete diagnostic fixture. -- [ ] Verify CSS remains opt-in through the existing `styles.css` export and the pure subpath has no CSS side effect. -- [ ] Run: - -```bash -pnpm build -pnpm verify:package -pnpm exec vitest run src/packageExports.test.ts src/packageConsumer.test.ts src/writing-diagnostics/packageBoundary.test.ts -``` +- [ ] Test rendering, keyboard/touch navigation, focus, Apply, undo, strict invalidation, zoom, forced colors, and mobile targets on pinned Chromium/Firefox/WebKit. +- [ ] Prove SSR renders without browser globals, segmenter, model infrastructure, or semantic evaluation; hydration adds no duplicate IDs, focus theft, or mismatch. +- [ ] Test script/HTML strings, bidi controls, isolated surrogates, nulls, oversize, accessors, proxies, duplicate JSON members, and callback exceptions. +- [ ] Use multilingual semantic contrast fixtures; without host diagnostics the exact result is an empty diagnostic surface. +- [ ] Record browser lock, artifact identity, and the distinction between editor integrity evidence and model accuracy. -- [ ] Commit: +## Task 10: canonical documentation and traceability -```bash -git add package.json pnpm-lock.yaml vite.writing-diagnostics.config.ts scripts/verify-package.mjs src/packageExports.test.ts src/packageConsumer.test.ts src/writing-diagnostics/packageBoundary.test.ts -git commit -m "build(diagnostics): publish framework-neutral contracts" -``` +**Files:** README, Architecture, PRD, TRD, API contract, threat model, operability, traceability, ADR 0027/0028/index, design, this plan, CHANGELOG, documentation-contract tests. -## Task 9: Add Browser, SSR, Hostile-Input, and No-Fallback Assurance - -**Files:** -- Create: `tests/browser/specs/writing-diagnostics.browser.spec.ts` -- Modify: `tests/browser/fixture/index.html` -- Create: `src/components/writingDiagnosticsSecurity.test.tsx` -- Modify: `src/components/editorServerRendering.test.tsx` -- Modify: `docs/TEST_STRATEGY.md` - -- [ ] Add Playwright scenarios in Chromium, Firefox, and WebKit for rendering, keyboard navigation, range focus, apply, undo, stale invalidation, zoom, forced-colors, and mobile/touch action targets. -- [ ] Add SSR tests proving a deterministic initial shell renders without `window`, `document`, `Intl.Segmenter`, model infrastructure, or diagnostics evaluation. -- [ ] Add hydration tests proving diagnostics verify after the client editor is ready without duplicate IDs, focus theft, or markup mismatch. -- [ ] Add hostile-input tests for HTML/script strings, bidi controls, isolated surrogates, nulls, oversized values, accessors, proxies, duplicate keys after JSON parsing, and callback exceptions. -- [ ] Add semantic contrast fixtures proving Inkspan itself has no keyword behavior: - - identical words in a quotation and direct statement; - - the same issue expressed through unrelated paraphrases; - - product names, code, URLs, and paths resembling spelling errors; - - Korean, English, mixed-language, and CJK documents. -- [ ] The expected Inkspan result for every contrast fixture without host diagnostics is an empty diagnostic surface. -- [ ] Document that these tests establish editor integrity and rendering parity, not the accuracy of any LLM or host rubric. -- [ ] Run the repository-pinned browser workflow command used by CI and record the exact browser package lock and artifact receipt. -- [ ] Commit: - -```bash -git add tests/browser/specs/writing-diagnostics.browser.spec.ts tests/browser/fixture/index.html src/components/writingDiagnosticsSecurity.test.tsx src/components/editorServerRendering.test.tsx docs/TEST_STRATEGY.md -git commit -m "test(diagnostics): prove browser and no-fallback assurance" -``` +- [ ] Document host semantic authority versus Inkspan deterministic integrity. +- [ ] Synchronize public type examples, strict invalidation, semantic-neutral decorations, collapsed selectors, single-action application, standalone/collaboration parity, SSR, degraded operation, privacy, and rollback. +- [ ] Remove temporary errata after folding every rule into the canonical ADR/design/plan; leave no parallel instruction set. +- [ ] Document that confidence/priority/category are host labels, not editor truth or submission policy. +- [ ] Add threats for hostile diagnostics, stale async work, replacement injection, telemetry leakage, focus attacks, collaboration races, and authority confusion. +- [ ] Map requirements to modules, tests, browser/package evidence, and release gates. +- [ ] Keep ADRs Proposed until protected implementation and acceptance exist. +- [ ] Add machine contracts that reject semantic keyword fallback, category-derived semantic ARIA, cross-edit mapping, provider ownership, stale repair, batch mutation authority, and send gating. -## Task 10: Reconcile Canonical Documentation and Traceability - -**Files:** -- Modify: `README.md` -- Modify: `ARCHITECTURE.md` -- Modify: `docs/PRD.md` -- Modify: `docs/TRD.md` -- Modify: `docs/API_CONTRACT.md` -- Modify: `docs/THREAT_MODEL.md` -- Modify: `docs/OPERABILITY.md` -- Modify: `docs/TRACEABILITY.md` -- Modify: `docs/adr/0027-host-owned-llm-writing-diagnostics.md` -- Modify: `docs/adr/README.md` -- Modify: `CHANGELOG.md` -- Modify: `src/documentationContracts.test.ts` - -- [ ] Add public examples for host-supplied diagnostics, exact revision capture, action callbacks, stale refresh, standalone and collaborative editors, SSR, and no-model degraded operation. -- [ ] Document the distinction between semantic authority and deterministic integrity. -- [ ] Document that `confidence` and `priority` are host evidence labels, not editor truth or submission policy. -- [ ] Add a threat-model section for prompt/model output as untrusted data, hostile diagnostic objects, stale selectors, replacement injection, overlap conflicts, telemetry leakage, focus attacks, and collaboration races. -- [ ] Add an operability section for review-unavailable state, invalid diagnostics, refresh ownership, callback failure, feature rollback, and no-network/offline behavior. -- [ ] Update traceability from ADR requirement to source module, test, browser evidence, package evidence, and release gate. -- [ ] Keep ADR 0027 `Proposed` until protected `main` contains the implementation and exact-head acceptance evidence; promote it in the release reconciliation PR, not prematurely. -- [ ] Record the feature under `Unreleased` without claiming Naruon integration, LLM quality, language validation, or publication. -- [ ] Add documentation contract tests that fail if keyword fallback, provider ownership, stale-repair, or send-gating claims reappear. -- [ ] Run: - -```bash -pnpm exec vitest run src/documentationContracts.test.ts -pnpm typecheck -``` +## Task 11: exact-head integration acceptance -- [ ] Commit: +- [ ] Reconcile the complete stack onto the latest protected main without destructive history or lost concurrent changes. +- [ ] Remove every temporary branch-specific workflow and regenerate evidence on the resulting exact head. +- [ ] Run full tests, typecheck, exact 100% owned coverage, library/demo builds, packed consumers, cross-engine browsers, Office Python 3.11/3.14 package gates, security/SAST/dependency/SBOM/provenance/secret checks, and documentation contracts. +- [ ] Inspect all current human/CodeRabbit/GHAS/Dependabot/OpenCode/Noema/Strix feedback and resolve only addressed threads. +- [ ] Require zero valid unresolved findings and qualifying independent non-author approval on the unchanged head. +- [ ] Mark Ready and merge only through protected policy; then verify protected main contains expected files and no temporary artifacts. -```bash -git add README.md ARCHITECTURE.md docs/PRD.md docs/TRD.md docs/API_CONTRACT.md docs/THREAT_MODEL.md docs/OPERABILITY.md docs/TRACEABILITY.md docs/adr/0027-host-owned-llm-writing-diagnostics.md docs/adr/README.md CHANGELOG.md src/documentationContracts.test.ts -git commit -m "docs(diagnostics): reconcile product and assurance contracts" -``` +## Task 12: release-only publication and host handoff -## Task 11: Exact-Head Acceptance and Merge - -- [ ] Rebase or merge the latest protected `main` without discarding valid concurrent changes. -- [ ] Run the complete repository test suite. -- [ ] Run `pnpm typecheck`. -- [ ] Run `pnpm coverage` and prove 100% production statement, branch, function, and line coverage. -- [ ] Run deterministic demo/library builds. -- [ ] Run `pnpm verify:package` against the packed tarball outside the source tree. -- [ ] Run cross-engine browser evidence through the repository-pinned Playwright lane. -- [ ] Run Office Python 3.11 and 3.14 test, docstring, branch coverage, wheel, schema, and license gates. -- [ ] Run SAST, dependency, supply-chain, SBOM, provenance, and secret checks on the exact final head. -- [ ] Review every current-head CodeRabbit, GitHub Advanced Security, Dependabot, OpenCode, Noema, Strix, human, and other applicable finding. -- [ ] Resolve every valid review thread and rerun affected tests. -- [ ] Confirm zero unresolved valid review threads. -- [ ] Obtain a qualifying non-author current-head approval. -- [ ] Move the PR from Draft to Ready only after implementation, direct validation, and documentation gates are complete. -- [ ] Merge without bypass only after all protected exact-head checks and approval rules pass. -- [ ] Refetch protected `main` and verify the merge commit contains the expected files and no unrelated branch artifacts. - -## Task 12: Release-Only Publication and Naruon Handoff - -- [ ] Open a separate release-only PR for the next compatible minor version after the feature merge. -- [ ] Promote ADR 0027 to `Accepted` only with protected-main implementation and exact-head evidence. -- [ ] Update version metadata, final CHANGELOG release section, package declarations, license inventory, SBOM, provenance, and rollback evidence. -- [ ] Publish immutable npm artifacts only from the exact reviewed release head. -- [ ] Verify ESM, CommonJS, types, CSS, root, collaboration, and `writing-diagnostics` subpaths from the published package. -- [ ] Record the immutable version, tarball integrity, source commit, package manifest, and compatibility matrix in the Naruon companion PR. -- [ ] Do not merge Naruon's runtime integration against a mutable Inkspan branch, source archive, local path, or unreviewed package. -- [ ] Retain a documented rollback path that removes diagnostic props and UI without document migration or canonical-envelope changes. +- [ ] Open a separate release-only PR after protected feature integration and current release-train closure. +- [ ] Promote ADR status only with protected implementation and current acceptance evidence. +- [ ] Update version, final CHANGELOG, package declarations, licenses, SBOM/provenance, compatibility, and rollback. +- [ ] Publish immutable npm artifacts from the exact reviewed release head and verify all subpaths from the registry artifact. +- [ ] Record immutable version, tarball digest, source commit, manifest, browser evidence, and rollback in the Naruon companion integration. +- [ ] Never integrate a mutable branch, source archive, copied fork, local path, or unreviewed package into a host runtime. From b79d54969c295fbfd4d957c6172af2c7d4c1b132 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 20:55:38 +0900 Subject: [PATCH 16/18] test(docs): normalize Markdown code spans in prose contracts --- src/writingDiagnosticsDocumentationContract.test.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/writingDiagnosticsDocumentationContract.test.ts b/src/writingDiagnosticsDocumentationContract.test.ts index 03053c44..9de70450 100644 --- a/src/writingDiagnosticsDocumentationContract.test.ts +++ b/src/writingDiagnosticsDocumentationContract.test.ts @@ -8,9 +8,15 @@ function repositoryFile(path: string): string { return readFileSync(resolve(process.cwd(), path), 'utf8'); } -/** Collapse prose whitespace so Markdown wrapping cannot weaken the contract. */ +/** + * Collapse prose whitespace and code-span punctuation without weakening words. + * + * Markdown authors may correctly wrap identifiers such as `docChanged` or + * `aria-invalid` in code spans. The documentation contract checks the semantic + * prose, not that presentational punctuation choice. + */ function normalizeProse(value: string): string { - return value.replace(/\s+/gu, ' ').trim(); + return value.replace(/`/gu, '').replace(/\s+/gu, ' ').trim(); } const designPath = From 9c26b0fae9cf7363fadd16c72efc6b0278e667db Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 04:08:56 +0900 Subject: [PATCH 17/18] fix(docs): resolve writing diagnostics ADR numbering --- ...tics.md => 0028-host-owned-llm-writing-diagnostics.md} | 2 +- ...=> 0029-writing-diagnostics-v1-strict-invalidation.md} | 8 ++++---- docs/adr/README.md | 4 ++-- .../2026-08-12-writing-diagnostics-implementation.md | 2 +- .../2026-08-12-writing-diagnostics-v1-contract-errata.md | 4 ++-- ...08-12-revision-bound-llm-writing-diagnostics-design.md | 2 +- src/writingDiagnosticsDocumentationContract.test.ts | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) rename docs/adr/{0027-host-owned-llm-writing-diagnostics.md => 0028-host-owned-llm-writing-diagnostics.md} (99%) rename docs/adr/{0028-writing-diagnostics-v1-strict-invalidation.md => 0029-writing-diagnostics-v1-strict-invalidation.md} (97%) diff --git a/docs/adr/0027-host-owned-llm-writing-diagnostics.md b/docs/adr/0028-host-owned-llm-writing-diagnostics.md similarity index 99% rename from docs/adr/0027-host-owned-llm-writing-diagnostics.md rename to docs/adr/0028-host-owned-llm-writing-diagnostics.md index 25e98da8..3f903133 100644 --- a/docs/adr/0027-host-owned-llm-writing-diagnostics.md +++ b/docs/adr/0028-host-owned-llm-writing-diagnostics.md @@ -1,4 +1,4 @@ -# ADR 0027: Host-owned, revision-bound writing diagnostics +# ADR 0028: Host-owned, revision-bound writing diagnostics Status: Proposed diff --git a/docs/adr/0028-writing-diagnostics-v1-strict-invalidation.md b/docs/adr/0029-writing-diagnostics-v1-strict-invalidation.md similarity index 97% rename from docs/adr/0028-writing-diagnostics-v1-strict-invalidation.md rename to docs/adr/0029-writing-diagnostics-v1-strict-invalidation.md index 10587ee0..cd1eb573 100644 --- a/docs/adr/0028-writing-diagnostics-v1-strict-invalidation.md +++ b/docs/adr/0029-writing-diagnostics-v1-strict-invalidation.md @@ -1,10 +1,10 @@ -# ADR 0028: Strict invalidation and semantic-neutral accessibility for writing diagnostics v1 +# ADR 0029: Strict invalidation and semantic-neutral accessibility for writing diagnostics v1 Status: Proposed ## Context -ADR 0027, its design specification, and its implementation plan established the correct high-level boundary: hosts own every semantic writing judgment while Inkspan owns deterministic validation, revision/selector integrity, presentation, accessibility, and ordinary editor transactions. +ADR 0028, its design specification, and its implementation plan established the correct high-level boundary: hosts own every semantic writing judgment while Inkspan owns deterministic validation, revision/selector integrity, presentation, accessibility, and ordinary editor transactions. Two lower-level clauses nevertheless permitted incompatible first-release interpretations: @@ -21,7 +21,7 @@ Rejected for v1. ProseMirror can map structural positions, but position continui ### Derive ARIA validity from `categoryCode` -Rejected. `categoryCode` is host-defined opaque metadata. Comparing it with strings such as `spelling`, `grammar`, `mechanics`, or language-specific equivalents would be the semantic keyword fallback that ADR 0027 explicitly prohibits. +Rejected. `categoryCode` is host-defined opaque metadata. Comparing it with strings such as `spelling`, `grammar`, `mechanics`, or language-specific equivalents would be the semantic keyword fallback that ADR 0028 explicitly prohibits. ### Add an explicit semantic ARIA enum to v1 @@ -43,7 +43,7 @@ For writing diagnostics v1: - Inkspan does not derive `aria-invalid`, spelling/grammar state, or any other semantic accessibility assertion from `categoryCode`, title, explanation, replacement, confidence, provenance, or source text; - category, priority, title, explanation, and actions remain available as plain text in the named diagnostics panel, while underlines are a visual supplement rather than the sole information channel. -This ADR narrows and supersedes only the conflicting lifecycle and decoration-accessibility clauses in ADR 0027, the 2026-08-12 design specification, and the original implementation plan. Their product/host/model authority, security, privacy, revision, packaging, and release decisions remain in force. +This ADR narrows and supersedes only the conflicting lifecycle and decoration-accessibility clauses in ADR 0028, the 2026-08-12 design specification, and the original implementation plan. Their product/host/model authority, security, privacy, revision, packaging, and release decisions remain in force. ## Consequences diff --git a/docs/adr/README.md b/docs/adr/README.md index 8c314016..3b5a5919 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -32,8 +32,8 @@ This index records durable architectural decisions. An ADR on a branch or protec | [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 | -| [0027](0027-host-owned-llm-writing-diagnostics.md) | Proposed | Host-owned, revision-bound LLM writing diagnostics | -| [0028](0028-writing-diagnostics-v1-strict-invalidation.md) | Proposed | Strict writing-diagnostic invalidation and semantic-neutral accessibility | +| [0028](0028-host-owned-llm-writing-diagnostics.md) | Proposed | Host-owned, revision-bound LLM writing diagnostics | +| [0029](0029-writing-diagnostics-v1-strict-invalidation.md) | Proposed | Strict writing-diagnostic invalidation and semantic-neutral accessibility | ## Decision discipline diff --git a/docs/superpowers/plans/2026-08-12-writing-diagnostics-implementation.md b/docs/superpowers/plans/2026-08-12-writing-diagnostics-implementation.md index 15b686e8..8182bab4 100644 --- a/docs/superpowers/plans/2026-08-12-writing-diagnostics-implementation.md +++ b/docs/superpowers/plans/2026-08-12-writing-diagnostics-implementation.md @@ -132,7 +132,7 @@ data-cwl-diagnostic-id="opaque-id" ## Task 10: canonical documentation and traceability -**Files:** README, Architecture, PRD, TRD, API contract, threat model, operability, traceability, ADR 0027/0028/index, design, this plan, CHANGELOG, documentation-contract tests. +**Files:** README, Architecture, PRD, TRD, API contract, threat model, operability, traceability, ADR 0028/0029/index, design, this plan, CHANGELOG, documentation-contract tests. - [ ] Document host semantic authority versus Inkspan deterministic integrity. - [ ] Synchronize public type examples, strict invalidation, semantic-neutral decorations, collapsed selectors, single-action application, standalone/collaboration parity, SSR, degraded operation, privacy, and rollback. diff --git a/docs/superpowers/plans/2026-08-12-writing-diagnostics-v1-contract-errata.md b/docs/superpowers/plans/2026-08-12-writing-diagnostics-v1-contract-errata.md index a377f872..ed7e7e95 100644 --- a/docs/superpowers/plans/2026-08-12-writing-diagnostics-v1-contract-errata.md +++ b/docs/superpowers/plans/2026-08-12-writing-diagnostics-v1-contract-errata.md @@ -2,7 +2,7 @@ Status: Required companion to `2026-08-12-writing-diagnostics-implementation.md` -ADR 0028 resolves two contradictions discovered after the original atomic implementation plan was written. Every remaining task and acceptance review must apply this errata. Task 10 must fold these corrections into the canonical ADR, design, plan, PRD, TRD, contracts, threat model, operability, traceability, and CHANGELOG so the final protected-main documentation has no parallel instruction set. +ADR 0029 resolves two contradictions discovered after the original atomic implementation plan was written. Every remaining task and acceptance review must apply this errata. Task 10 must fold these corrections into the canonical ADR, design, plan, PRD, TRD, contracts, threat model, operability, traceability, and CHANGELOG so the final protected-main documentation has no parallel instruction set. ## Global lifecycle correction @@ -61,7 +61,7 @@ Cross-engine and hostile-input evidence must prove: ## Task 10 reconciliation requirement -Before the feature stack can become Ready, the original ADR 0027 and design/plan prose must be edited so they directly express ADR 0028. This errata is temporary planning evidence, not the desired final duplicate source of truth. The final documentation contract tests must fail if either of the superseded claims reappears: +Before the feature stack can become Ready, the original ADR 0028 and design/plan prose must be edited so they directly express ADR 0029. This errata is temporary planning evidence, not the desired final duplicate source of truth. The final documentation contract tests must fail if either of the superseded claims reappears: 1. a diagnostic can survive `docChanged` through transaction mapping; or 2. Inkspan derives `aria-invalid` or other semantic state from an opaque category string. diff --git a/docs/superpowers/specs/2026-08-12-revision-bound-llm-writing-diagnostics-design.md b/docs/superpowers/specs/2026-08-12-revision-bound-llm-writing-diagnostics-design.md index 75e26e15..4b94190f 100644 --- a/docs/superpowers/specs/2026-08-12-revision-bound-llm-writing-diagnostics-design.md +++ b/docs/superpowers/specs/2026-08-12-revision-bound-llm-writing-diagnostics-design.md @@ -297,7 +297,7 @@ The feature remains `Unreleased`. It may ship only after the complete stack is r - RFC 9110 for strong entity-tag semantics used by Inkspan revision evidence. - Inkspan ADR 0011 for deterministic versus model-assisted authoring. - Inkspan ADR 0018 for revision-scoped W3C selector authority. -- Inkspan ADR 0027 and ADR 0028 for host semantic authority, strict invalidation, and semantic-neutral accessibility. +- Inkspan ADR 0028 and ADR 0029 for host semantic authority, strict invalidation, and semantic-neutral accessibility. - The accompanying doctoring record for LLM-judge bias and host calibration implications. ## Approval boundary diff --git a/src/writingDiagnosticsDocumentationContract.test.ts b/src/writingDiagnosticsDocumentationContract.test.ts index 9de70450..75dce9fc 100644 --- a/src/writingDiagnosticsDocumentationContract.test.ts +++ b/src/writingDiagnosticsDocumentationContract.test.ts @@ -23,9 +23,9 @@ const designPath = 'docs/superpowers/specs/2026-08-12-revision-bound-llm-writing-diagnostics-design.md'; const planPath = 'docs/superpowers/plans/2026-08-12-writing-diagnostics-implementation.md'; -const adrPath = 'docs/adr/0027-host-owned-llm-writing-diagnostics.md'; +const adrPath = 'docs/adr/0028-host-owned-llm-writing-diagnostics.md'; const supersedingAdrPath = - 'docs/adr/0028-writing-diagnostics-v1-strict-invalidation.md'; + 'docs/adr/0029-writing-diagnostics-v1-strict-invalidation.md'; describe('writing diagnostics documentation contract', () => { it('keeps the public design examples synchronized with the implemented v1 types', () => { From 8b30f8e9e15a645d281455687644d5eb9a07b7ef Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 04:25:05 +0900 Subject: [PATCH 18/18] fix(ci): reconcile release workflow with protected main --- .github/workflows/release.yml | 94 ++++++++++++++++++++++++++++++----- 1 file changed, 82 insertions(+), 12 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cfb80a5a..2cabb6da 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,7 +26,7 @@ jobs: with: fetch-depth: 0 - name: Set up pnpm - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10 - name: Set up Node.js uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: @@ -91,7 +91,8 @@ jobs: mv "$package_file" release/ - name: Install hash-locked Office dependencies working-directory: office - run: python -m pip install --require-hashes --only-binary=:all: -r requirements-ci.txt + run: | + python -m pip install --require-hashes --only-binary=:all: -r requirements-ci.txt - name: Verify Office dependency consistency working-directory: office run: python -m pip check @@ -121,11 +122,65 @@ jobs: assert any(name.endswith('.dist-info/licenses/LICENSE') for name in names) PY mv dist/*.whl ../release/ + - name: Install Cosign + uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 + with: + cosign-release: 'v3.0.6' + - name: Install signature-verified Syft + run: | + set -euo pipefail + syft_installer="$RUNNER_TEMP/syft-install.sh" + curl --fail --silent --show-error --location \ + --proto '=https' \ + --output "$syft_installer" \ + https://raw.githubusercontent.com/anchore/syft/16223e6dd7893fe578787658ceb876257483d404/install.sh + mkdir -p "$RUNNER_TEMP/syft-bin" + DOWNLOAD_TAG_INSTALL_SCRIPT=false \ + sh "$syft_installer" -v -b "$RUNNER_TEMP/syft-bin" v1.50.0 + "$RUNNER_TEMP/syft-bin/syft" version + echo "$RUNNER_TEMP/syft-bin" >> "$GITHUB_PATH" + - name: Generate release SBOM + run: | + set -euo pipefail + syft scan dir:. -o spdx-json > release/inkspan.spdx.json + - name: Validate release SBOM + run: | + set -euo pipefail + node <<'NODE' + const { readFileSync, statSync } = require('node:fs'); + + const sbomPath = 'release/inkspan.spdx.json'; + const sbom = JSON.parse(readFileSync(sbomPath, 'utf8')); + const packageMetadata = JSON.parse(readFileSync('package.json', 'utf8')); + const officeMetadata = readFileSync('office/pyproject.toml', 'utf8'); + if (statSync(sbomPath).size > 16 * 1024 * 1024) { + throw new Error('Release SBOM exceeds the 16 MiB actions/attest input limit.'); + } + if (sbom.spdxVersion !== 'SPDX-2.3') { + throw new Error(`Release SBOM must be SPDX-2.3; found ${sbom.spdxVersion ?? 'missing'}.`); + } + if (!Array.isArray(sbom.packages) || sbom.packages.length === 0) { + throw new Error('Release SBOM package inventory must not be empty.'); + } + const sbomPackageNames = new Set(sbom.packages.map((pkg) => pkg.name)); + if (packageMetadata.name !== '@contextualwisdomlab/cwl-editor') { + throw new Error('Release source has an unexpected editor package identity.'); + } + if (!/^name\s*=\s*["']inkspan-office["']\s*$/m.test(officeMetadata)) { + throw new Error('Release source has an unexpected Office package identity.'); + } + if (!sbomPackageNames.has(packageMetadata.name)) { + throw new Error('Release SBOM inventory must include the editor package identity.'); + } + if (!sbomPackageNames.has('inkspan-office')) { + throw new Error('Release SBOM inventory must include the Office package identity.'); + } + NODE - name: Generate release checksums run: | set -euo pipefail cd release - sha256sum -- *.tgz *.whl > SHA256SUMS + sha256sum -- *.tgz *.whl inkspan.spdx.json > SHA256SUMS - name: Transfer exact release artifacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: @@ -152,7 +207,7 @@ jobs: ref: ${{ github.sha }} persist-credentials: false - name: Set up pnpm - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10 - name: Set up Node.js uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: @@ -237,7 +292,7 @@ jobs: - name: Verify bounded local release artifact set run: | set -euo pipefail - expected_asset_count=3 + expected_asset_count=4 mapfile -t local_entries < <( find release -mindepth 1 -maxdepth 1 -printf '%f\n' | LC_ALL=C sort ) @@ -254,8 +309,9 @@ jobs: || ${#local_assets[@]} -ne $expected_asset_count \ || ${#npm_assets[@]} -ne 1 \ || ${#wheel_assets[@]} -ne 1 \ + || ! -f release/inkspan.spdx.json \ || ! -f release/SHA256SUMS ]]; then - echo "::error::Unexpected local release artifact set; require exactly one *.tgz, one *.whl, and SHA256SUMS." + echo "::error::Unexpected local release artifact set; require exactly one *.tgz, one *.whl, inkspan.spdx.json, and SHA256SUMS." exit 1 fi - name: Attest release artifacts @@ -264,15 +320,28 @@ jobs: subject-path: | release/*.tgz release/*.whl + release/inkspan.spdx.json release/SHA256SUMS + - name: Attest release packages with SBOM + uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + with: + subject-path: | + release/*.tgz + release/*.whl + sbom-path: release/inkspan.spdx.json - name: Verify generated attestations env: GH_TOKEN: ${{ github.token }} run: | set -euo pipefail - for artifact in release/*.tgz release/*.whl release/SHA256SUMS; do + for artifact in release/*.tgz release/*.whl release/inkspan.spdx.json release/SHA256SUMS; do gh attestation verify "$artifact" --repo "$GITHUB_REPOSITORY" done + for artifact in release/*.tgz release/*.whl; do + gh attestation verify "$artifact" \ + --repo "$GITHUB_REPOSITORY" \ + --predicate-type https://spdx.dev/Document/v2.3 + done - name: Prepare draft GitHub release env: GH_TOKEN: ${{ github.token }} @@ -304,7 +373,7 @@ jobs: GH_TOKEN: ${{ github.token }} run: | set -euo pipefail - expected_asset_count=3 + expected_asset_count=4 mapfile -t local_entries < <( find release -mindepth 1 -maxdepth 1 -printf '%f\n' | LC_ALL=C sort ) @@ -321,8 +390,9 @@ jobs: || ${#local_assets[@]} -ne $expected_asset_count \ || ${#npm_assets[@]} -ne 1 \ || ${#wheel_assets[@]} -ne 1 \ + || ! -f release/inkspan.spdx.json \ || ! -f release/SHA256SUMS ]]; then - echo "::error::Unexpected local release artifact set; require exactly one *.tgz, one *.whl, and SHA256SUMS." + echo "::error::Unexpected local release artifact set; require exactly one *.tgz, one *.whl, inkspan.spdx.json, and SHA256SUMS." exit 1 fi @@ -414,7 +484,7 @@ jobs: fi gh release verify "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" - for artifact in release/*.tgz release/*.whl release/SHA256SUMS; do + for artifact in release/*.tgz release/*.whl release/inkspan.spdx.json release/SHA256SUMS; do gh release verify-asset "$GITHUB_REF_NAME" "$artifact" \ --repo "$GITHUB_REPOSITORY" done @@ -600,7 +670,7 @@ jobs: process.exit(2); } process.stdout.write(url.origin); - NODE + NODE )" || { echo "::error::npm dist.tarball must stay on the canonical registry.npmjs.org HTTPS origin." exit 1 @@ -646,4 +716,4 @@ jobs: done echo "::error::Registry publication verification did not converge to the exact artifact digests." - exit 1 + exit 1 \ No newline at end of file