Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
a166919
test(docs): define modular architecture contract
seonghobae Aug 5, 2026
b7e5a91
docs(architecture): define standalone and modular MSA boundary
seonghobae Aug 5, 2026
62714aa
docs(naruon): add compose and ui.panel integration guide
seonghobae Aug 5, 2026
6267a76
docs(doctoring): record naruon modular architecture decision
seonghobae Aug 5, 2026
7fa0315
docs(changelog): record modular architecture slice
seonghobae Aug 5, 2026
93b28eb
fix(docs): align naruon sample with autosave API
seonghobae Aug 5, 2026
2a4179b
test(docs): guard autosave capture ordering
seonghobae Aug 5, 2026
941374c
fix(docs): prevent stale autosave capture ordering
seonghobae Aug 5, 2026
ff8ea7d
test(docs): require isolated authorized editing contexts
seonghobae Aug 5, 2026
84b610e
fix(docs): isolate authorized editor session lifecycles
seonghobae Aug 5, 2026
5ca3ac2
test(docs): reject memoized autosave session identity
seonghobae Aug 5, 2026
8d472d7
fix(docs): bind autosave identity to keyed client state
seonghobae Aug 5, 2026
3c6dbff
docs(doctoring): record authorized editing-context isolation
seonghobae Aug 5, 2026
8be2b5f
docs(changelog): record isolated naruon editing contexts
seonghobae Aug 5, 2026
edf5f0f
fix(docs): preserve editing-context contract phrase
seonghobae Aug 5, 2026
ae0debd
fix(docs): preserve hydration contract phrase
seonghobae Aug 5, 2026
eb784d0
test(docs): require bounded recoverable panel example
seonghobae Aug 5, 2026
3dd8434
docs: bound autosave recovery and panel labels
seonghobae Aug 5, 2026
fb02774
docs: record bounded recovery verification
seonghobae Aug 5, 2026
2f80350
docs: record bounded panel recovery contract
seonghobae Aug 5, 2026
43a211b
test: require stale-generation conflict recovery
seonghobae Aug 6, 2026
984a35b
test: bind conflict recovery doctoring
seonghobae Aug 6, 2026
bdfb751
docs: preserve recovery across newer edits
seonghobae Aug 6, 2026
01ee6a6
docs: record stale conflict recovery decision
seonghobae Aug 6, 2026
ba82ebb
docs: record stale-generation conflict recovery
seonghobae Aug 6, 2026
10a79e7
docs: make recovery invariant verifiable
seonghobae Aug 6, 2026
14096f3
test: require exact-head read-only CI
seonghobae Aug 6, 2026
e68781a
test: bind exact-head CI doctoring
seonghobae Aug 6, 2026
bf348d6
ci: verify immutable pull request heads
seonghobae Aug 6, 2026
6cc2c2a
docs: record exact-head CI boundary
seonghobae Aug 6, 2026
ed404df
docs: record exact-head read-only CI
seonghobae Aug 6, 2026
f6cdcf4
test: require operational autosave recovery
seonghobae Aug 6, 2026
dd8edbe
docs: recover blocked operational saves
seonghobae Aug 6, 2026
d47f8c4
docs: record operational autosave recovery
seonghobae Aug 6, 2026
021481d
docs: record operational save recovery
seonghobae Aug 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,17 @@ concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
build-and-test:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
persist-credentials: false
# pnpm version comes from the package.json "packageManager" field
# (pnpm 10+), which is required to read `overrides` from
# pnpm-workspace.yaml consistently with the committed lockfile.
Expand All @@ -40,7 +46,7 @@ jobs:

office:
name: Office / Python ${{ matrix.python-version }}
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
Expand All @@ -51,7 +57,10 @@ jobs:
env:
PYTHONPATH: src
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: ${{ matrix.python-version }}
Expand Down
169 changes: 169 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# Inkspan Architecture

Inkspan is a standalone authoring product and an embeddable module for CWL
applications. The architecture deliberately separates deterministic editor and
conversion behavior from host-owned transport, identity, tenancy, persistence,
and model policy so the same package can run independently or inside a modular
MSA composition.

## Standalone product boundary

Inkspan owns editor and deterministic conversion surfaces.

The standalone package provides:

- Markdown and HTML authoring through TipTap and ProseMirror;
- strict link and inline-image validation;
- SSR-safe React hydration;
- provider-neutral Yjs collaboration bindings;
- canonical versioned document envelopes and strict UTF-8 bytes;
- SHA-256 revision evidence and local revision-guarded restore;
- bounded single-flight autosave coordination and durable strong-validator
session helpers;
- email serialization and framework-independent base64 conversion; and
- a network-free Office renderer for deterministic DOCX, XLSX, and PPTX output.

Hosts own transport, authorization, tenant isolation, persistence, credentials, migration, retention, and model-use policy.

Inkspan therefore never opens a production collaboration connection, chooses a
tenant, stores a provider secret, creates a durable database transaction, decides
a retention schedule, or authorizes an AI operation. A standalone adopter can
provide those capabilities directly; a CWL host can provide them through shared
platform services.

## Modular MSA composition

The modular boundary is intentionally additive. Importing Inkspan does not
require naruon or contextual-orchestrator, while a CWL host can compose all
three without replacing Inkspan's deterministic local contracts.

```mermaid
flowchart LR
Browser[Browser or desktop shell]
Panel[naruon compose / ui.panel host]
Inkspan[Inkspan editor module]
Evidence[Revision evidence and autosave]
Collab[Host-owned Yjs provider]
Store[Host persistence service]
Models[contextual-orchestrator]
Office[Office renderer]
Control[ContextualWisdomLab/.github control plane]

Browser --> Panel
Panel --> Inkspan
Inkspan --> Evidence
Inkspan <--> Collab
Evidence --> Store
Panel --> Models
Inkspan --> Office
Control -. reusable CI, security, release policy .-> Inkspan
Control -. reusable CI, security, release policy .-> Panel
Control -. reusable CI, security, release policy .-> Models
```

### Component responsibilities

| Component | Owns | Must not assume |
| --- | --- | --- |
| Inkspan | Editing, deterministic import/export, canonical envelopes, local revision evidence, local autosave ordering, accessible editor controls | User identity, tenant authority, durable commit success, provider credentials, retention, or model policy |
| ContextualWisdomLab/naruon | Product composition, route and panel lifecycle, authenticated host API calls, accessible conflict and recovery UX | That local Inkspan revision evidence is a server commit or authorization grant |
| ContextualWisdomLab/contextual-orchestrator | Provider-neutral model routing and host-approved model execution policy | Direct ownership of editor state, tenant persistence, or collaboration transport |
| ContextualWisdomLab/.github | Reusable CI, security, review, provenance, and release policy | Runtime authorization or tenant data access |
| Host persistence service | Atomic writes, server-selected strong validators, tenant isolation, migration, encryption, retention, audit storage | That browser-side checks replace server-side validation |
| Host collaboration service | Connection, room authorization, awareness policy, update persistence, provider lifecycle | That Inkspan may create or destroy the host provider |

## Data ownership matrix

| Data or evidence | Local Inkspan responsibility | Host responsibility | Shareability |
| --- | --- | --- | --- |
| Editor document | Validate and transform deterministically | Authorize access, persist, encrypt, migrate, retain | Private unless host policy explicitly permits sharing |
| Canonical envelope | Produce and validate exact schema/version bytes | Store, sign, classify, migrate, and apply retention | Usually private; contains the complete document |
| Local SHA-256 revision | Detect local equality and guard local restore | Never treat as authorization or durable commit evidence | Metadata only under tenant policy |
| Server-selected strong `ETag` | Validate syntax before use in a session | Select atomically and enforce `If-Match` in the write transaction | Tenant-confidential concurrency metadata |
| Yjs updates and awareness | Bind the supplied `Y.Doc` to the editor | Authorize rooms, transport, persist, redact, expire, and destroy providers | Host policy decides |
| Model prompt and output | Insert or restore validated results | Approve model use, credentials, redaction, routing, logging, and retention | Host policy decides |
| Release evidence | Expose deterministic tests and package contracts | Verify CI, provenance, approvals, and publication policy | Shareable only after secrets and tenant data are excluded |

## Optimistic-concurrency sequence

Inkspan coordinates local ordering, but the host service remains the only source
of durable success. The host returns a new server-selected strong `ETag` after
each accepted write. A local revision digest is never substituted for that
validator.

```mermaid
sequenceDiagram
participant U as Author
participant I as Inkspan
participant H as naruon or standalone host
participant S as Persistence service

U->>I: Edit document
I->>I: Capture immutable envelope and local revision
I->>H: Enqueue save evidence
H->>S: PUT document with If-Match: strong ETag
alt Current durable validator matches
S-->>H: 200/204 plus replacement strong ETag
H-->>I: saved with validated replacement validator
else Durable version changed
S-->>H: 412 Precondition Failed
H-->>I: conflict
I-->>U: Host renders accessible conflict, compare, merge, or fork flow
else Transport or ambiguous failure
S--xH: Failure without durable proof
H-->>I: blocked failure
I-->>U: Host requires explicit authenticated recovery
end
```

## SSR and panel lifecycle

A server-rendered host may render Inkspan's deterministic shell, but the
interactive editor, browser-only provider, and `Y.Doc` must be created in a
client boundary. In a Next.js App Router integration, naruon should keep the
`'use client'` boundary as small as practical and pass only serializable,
non-secret configuration into the panel. Provider secrets remain server-side.

The host owns provider creation and destruction. Inkspan may subscribe to the
supplied document and awareness state, but it must not create or destroy the
host provider. This allows one panel to mount and unmount without terminating a
provider shared by other product surfaces.

## Security and privacy boundaries

- Fail closed on malformed envelopes, unsafe links, external or active image
sources, invalid validators, unsupported versions, and ambiguous save results.
- Do not place full envelopes, conflict bodies, prompts, model output, provider
keys, access tokens, or tenant identifiers in ordinary logs or metrics.
- Treat local equality evidence separately from authenticated, shareable release
or audit evidence.
- Keep collaboration authorization, persistence authorization, and model-use
authorization independent even when the same user initiates all three.
- Pin reusable workflow sources immutably and require exact-head CI, security,
packaging, provenance, and independent review before release publication.
- Do not claim WCAG, OWASP ASVS, NIST, ISO, or protocol conformance from this
architecture document alone; verification belongs to the complete host and
deployed product.

## Acquisition evidence boundary

An acquisition reviewer should be able to verify the product without receiving
private tenant content or production credentials. Shareable evidence includes:

- source, licenses, dependency locks, SBOMs, immutable workflow pins, and release
provenance;
- exact-head unit, integration, security, accessibility, packaging, and release
results;
- public API declarations, architecture and operator records, migration
contracts, and rollback procedures; and
- deterministic fixtures that contain no customer data.

Local-only or restricted evidence includes production documents, conflict
bodies, Yjs updates, awareness metadata, provider credentials, authorization
claims, private model prompts and outputs, tenant-scoped validators, and
security findings that expose exploitable deployment detail.

The authoritative naruon composition guide is
[`docs/naruon-compose-ui-panel.md`](docs/naruon-compose-ui-panel.md). Standards,
claim boundaries, and decision history are recorded in
[`docs/doctoring/naruon-modular-architecture.md`](docs/doctoring/naruon-modular-architecture.md).
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ Historical release entries from **0.1.0 through 0.5.27** are preserved verbatim

## [Unreleased]

### Documentation
- Added an authoritative standalone and modular MSA architecture contract with reviewable deployment, optimistic-concurrency, data-ownership, security, and acquisition-evidence diagrams and tables
- Added a beginner-readable naruon compose and ui.panel integration guide covering narrow client hydration, server-selected strong validators, accessible conflict handling, host-owned Yjs lifecycle, contextual-orchestrator boundaries, and local-versus-shareable evidence
- Added an opaque editing-context remount for the complete editor and autosave example, latest-generation asynchronous capture ordering, encoded document path segments, redacted recovery status, and lazy state-owned session identity to prevent cross-document state reuse
- Bounded the host save example with a fresh abort deadline, exposed authenticated conflict recovery through `session.resume(...)`, generated an instance-unique accessible heading relationship, and strengthened fenced-TSX ordering contracts
- Added stale-generation conflict recovery and operational save failure recovery through one reason-aware single-flight host workflow, so newer local edits cannot hide or duplicate recovery while retained work remains blocked
- Added exact-head read-only CI with fixed Ubuntu 24.04 runners, immutable action pins, explicit contributor-head checkout, disabled persisted Git credentials, and a documented merge-result compatibility boundary
- Added deterministic documentation contract tests and APA 7th doctoring grounded in RFC 9110, WCAG 2.2, NIST SP 800-204, NIST SP 800-204D, OWASP ASVS 5.0.0, React, current Next.js App Router guidance, and GitHub Actions primary documentation

## [0.5.29] — 2026-08-05

### Added
Expand Down
144 changes: 144 additions & 0 deletions docs/doctoring/exact-head-ci-evidence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# Doctoring record: exact-head CI evidence

- **Status:** Accepted
- **Decision date:** 2026-08-06
- **Scope:** Repository-owned `CI` workflow only
- **Runtime change:** None; this changes verification source and runner controls

## Problem

GitHub's `pull_request` event exposes a synthetic pull-request merge ref as the
default checkout target. The prior Inkspan workflow used checkout defaults, so a
reported CI success described GitHub's temporary merge commit rather than the
immutable contributor head named in the pull request. The checkout action also
persisted its GitHub token into local Git configuration by default.

A synthetic merge test can be useful compatibility evidence, but it is not exact
source identity evidence. Treating it as the pull-request head can make reviews,
coverage reports, packages, and release claims appear bound to a commit that the
contributor branch never contained. Persisting credentials is also unnecessary
for a read-only build and enlarges the impact of untrusted build or test code.

## Decision

The repository-owned `CI` workflow checks out:

```yaml
ref: ${{ github.event.pull_request.head.sha || github.sha }}
persist-credentials: false
```

For a pull request, this selects the immutable contributor head. For a protected
branch push, it selects the event commit through `github.sha`. Both jobs use the
fixed `ubuntu-24.04` runner label and the immutable `actions/checkout` v7.0.1
commit. Workflow permissions remain `contents: read`, and no write permission,
secret-bearing model call, release publication, approval, or branch update is
introduced.

`FORCE_JAVASCRIPT_ACTIONS_TO_NODE24` is declared at workflow scope so the pinned
actions run under the repository's reviewed current JavaScript action runtime
rather than a deprecated implicit runtime.

## Evidence boundary

A successful run under this contract is exact-head CI evidence for the selected
source commit. It is not merge-result compatibility evidence. Branch protection,
a merge queue, or a separately reviewed integration test may still require a
fresh synthetic or protected-base integration result before merge.

The workflow result also does not replace Security Scan, Semgrep, CodeQL,
independent review, release provenance, package publication, or deployment
acceptance. Each required surface must bind its own result to the intended source
identity. In particular, an organization-required workflow that still checks a
synthetic merge ref must not be described as exact-head evidence merely because
its check appears on the pull request.

Local-versus-shareable evidence remains explicit. Checkout logs and exact source
SHAs are shareable release evidence. Repository tokens, runner credentials,
customer content, tenant identifiers, private callback output, and dependency
registry credentials are not.

## Security consequences

The contributor head is untrusted input. Disabling credential persistence keeps
the workflow token out of the checked-out repository's ordinary Git
configuration after checkout. The action still receives the job token long
enough to fetch the exact source, and the job retains only read permissions.

This is defense in depth rather than a sandbox claim. Test processes can still
read files and use the network permitted by the hosted runner. Secrets must not
be attached to this untrusted pull-request job, and a future write-capable step
must use a separate trusted workflow boundary rather than broadening this one.

Fixed runner labels reduce silent environment movement but do not make the hosted
image immutable. The exact image version remains visible in every job log.
Third-party actions are pinned to full commit identities, and the contract test
rejects mutable tags.

## Test-first evidence

Commit `14096f37f1aa47fb7f6661fea3b505193680aaf8` added the first workflow
contract before production changes. Commit
`e68781a8a2c96d5b103900e2622bf7de59925706` added this doctoring and changelog
contract before either record existed.

Pull-request CI run `31066658465` produced the intended red result: all 550 other
JavaScript tests and both Office Python matrix jobs passed, while the three new
contracts failed for `ubuntu-latest`, the missing Node 24 action-runtime policy,
and the absent doctoring record. Its checkout log also showed
`persist-credentials: true` and the synthetic `refs/pull/64/merge` source. That
run is historical TDD evidence, not success evidence.

Commit `bf348d6d2ad00589a990bc447f90548709533b4c` applied the workflow repair.
The final head must prove from its own checkout log that the exact contributor
head was selected and `persist-credentials: false` was effective before this
record can support merge readiness.

## Rejected alternatives

### Keep the default checkout ref and rename the claim

Rejected because the product and acquisition contract requires exact-head
coverage, packaging, and review evidence. A merge-only result cannot substitute
for the reviewed source identity.

### Use `pull_request_target`

Rejected because that event's privileged base-repository context is unsafe for
executing untrusted pull-request code. This read-only workflow needs no privileged
context.

### Keep persisted credentials for convenience

Rejected because no workflow step commits, pushes, tags, opens a pull request,
or publishes a release. Retaining the credential has no product benefit.

### Use mutable action tags or `ubuntu-latest`

Rejected because both silently change the verification implementation. Full
action commits and a fixed runner family make changes reviewable and reproducible
enough for this hosted-runner boundary.

## Rollback

Rollback restores the prior workflow and removes this record and its contract.
That rollback must also remove every exact-head claim because the default
pull-request checkout returns to a synthetic merge source and persisted Git
credentials. It does not provide a safe emergency path for publication or
approval.

No package version, database object, migration, runtime dependency, provider,
credential, scheduler, model call, or release is introduced.

## APA 7 references

GitHub, Inc. (n.d.). *Events that trigger workflows*. GitHub Docs. Retrieved
August 6, 2026, from
https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows

GitHub, Inc. (n.d.). *Workflow syntax for GitHub Actions*. GitHub Docs.
Retrieved August 6, 2026, from
https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax

GitHub, Inc. (n.d.). *actions/checkout*. GitHub. Retrieved August 6, 2026, from
https://github.com/actions/checkout
Loading
Loading