Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
1cdb527
test(autosave): specify durable validator session
seonghobae Aug 5, 2026
0491370
feat(autosave): implement durable validator session
seonghobae Aug 5, 2026
48900f5
build(autosave): export durable session surface
seonghobae Aug 5, 2026
d27462d
build(autosave): emit durable session declarations
seonghobae Aug 5, 2026
d1b98d8
test(autosave): distinguish callback assimilation failures
seonghobae Aug 5, 2026
2f44c26
test(package): verify durable autosave session consumers
seonghobae Aug 5, 2026
ba263ba
docs(changelog): record durable autosave session
seonghobae Aug 5, 2026
89c738d
docs(doctoring): record durable validator session
seonghobae Aug 5, 2026
1d3c2e5
test(autosave): preserve literal status in hostile proxy fixture
seonghobae Aug 5, 2026
1200a2c
docs(autosave): recommend durable validator session
seonghobae Aug 5, 2026
ad0630e
test(autosave): close durable session coverage gaps
seonghobae Aug 5, 2026
724ae6e
refactor(autosave): reuse validated queue evidence
seonghobae Aug 5, 2026
4e72133
test(autosave): cover malformed session option shapes
seonghobae Aug 5, 2026
749ef55
test(autosave): require descriptor-safe exact session options
seonghobae Aug 5, 2026
db2fd18
fix(autosave): validate exact session option descriptors
seonghobae Aug 5, 2026
3b81d85
test(autosave): pin recovery validator semantics
seonghobae Aug 5, 2026
5070a46
fix(autosave): validate recovery tags consistently
seonghobae Aug 5, 2026
030f904
test(autosave): harden durable session contracts
seonghobae Aug 5, 2026
e13886a
docs(changelog): record recovery contract hardening
seonghobae Aug 5, 2026
b7a05f5
test(autosave): cover declined recovery transition
seonghobae Aug 5, 2026
04d4af3
test(autosave): distinguish malformed recovery validators
seonghobae Aug 5, 2026
4305003
fix(autosave): expose recovery validator error code
seonghobae Aug 5, 2026
437e1ae
fix(autosave): distinguish invalid recovery validators
seonghobae Aug 5, 2026
0200e70
docs(autosave): record recovery validator error contract
seonghobae Aug 5, 2026
04a6b13
test(autosave): pin public recovery error code
seonghobae Aug 5, 2026
81bc902
test(autosave): expect dedicated recovery validator error
seonghobae Aug 5, 2026
b2ae61a
test(package): strengthen detached autosave session evidence
seonghobae Aug 5, 2026
e0dbff6
test(package): pin recovery error declaration export
seonghobae Aug 5, 2026
407df3e
test(autosave): require root recovery error type parity
seonghobae Aug 5, 2026
af96477
fix(autosave): align recovery error code contracts
seonghobae Aug 5, 2026
92ef91c
refactor(autosave): remove recovery error type escape
seonghobae Aug 5, 2026
b31d25e
test(autosave): expose flush recovery snapshot race
seonghobae Aug 5, 2026
6ee1ae5
test(autosave): expose flush close snapshot race
seonghobae Aug 5, 2026
2c5a159
fix(autosave): keep flush recovery snapshots atomic
seonghobae Aug 5, 2026
b10bcab
docs(autosave): clarify callback bounds and coherent flush snapshots
seonghobae Aug 5, 2026
9fc7a7c
docs(changelog): record coherent autosave flush snapshots
seonghobae Aug 5, 2026
b5da25f
docs(doctoring): record coherent autosave flush snapshots
seonghobae Aug 5, 2026
8cb1081
docs(changelog): align coherent autosave flush evidence
seonghobae Aug 5, 2026
2d6a418
release: prepare Inkspan 0.5.29
seonghobae Aug 5, 2026
7239a70
release: document Inkspan 0.5.29
seonghobae Aug 5, 2026
c193761
docs(doctoring): target Inkspan 0.5.29
seonghobae Aug 5, 2026
89470ca
chore(release): defer version bump until acceptance gates pass
seonghobae Aug 5, 2026
ca4fdba
chore(release): keep durable autosave changes unreleased
seonghobae Aug 5, 2026
8eb2c5c
docs(doctoring): defer release until acceptance gates
seonghobae Aug 5, 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
30 changes: 28 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,37 @@ Historical release entries from **0.1.0 through 0.5.27** are preserved verbatim

## [Unreleased]

### Added
- Added `createDocumentAutosaveSession()` to bind every single-flight save to the exact server-issued strong entity tag loaded or last committed by the host, without adding transport, persistence, credentials, tenancy, or provider coupling
- Added `isStrongHttpEntityTag()` for fail-closed RFC 9110 quoted opaque-tag validation without trimming or repair
- Added immutable durable save request, result, session, recovery, and document-free snapshot contracts to the framework-independent `autosave` package surface

### Reliability
- Successful durable writes advance the next `If-Match` base only from the host callback's validated server-selected replacement tag
- Conflict, malformed result, hostile reflection, promise-assimilation failure, and transport failure preserve the previous durable validator until explicit authenticated recovery supplies a new strong tag
- Retained work resumes only after `resume(nextStrongEntityTag)` installs the recovered durable base before the next callback begins
- Recovery validators are validated consistently before lifecycle inspection; a valid no-op resume cannot replace the current durable base, and an unexpectedly declined blocked transition restores the previous validator
- Malformed recovery validators now raise the dedicated public `invalid_recovery_validator` code and redacted recovery message instead of being misclassified as malformed session-construction options
- `flush()` reacquires the current terminal queue state after asynchronous wrapper boundaries, following recovery or shutdown races until lifecycle fields and `durableStrongEntityTag` describe one coherent idle, blocked, or closed logical moment

### Security
- Initial and replacement durable validators reject weak, unquoted, whitespace-containing, list, wildcard, control-character, and out-of-range values before they can enter host transport
- Callback results are exact-shape validated through property descriptors; document bodies, validators, callback values, and private exceptions never enter public error messages
- Session snapshots remain frozen and document-free; entity tags are still tenant-confidential equality metadata rather than authorization, signatures, tenant membership, or durable audit evidence

### Tests
- Added deterministic sequential and concurrently queued validator handoff, conflict recovery, lifecycle-independent recovery validation, malformed option, missing and symbol-keyed option, malformed callback result, hostile reflection, promise assimilation, transport failure, frozen request, shutdown, and snapshot tests under repository-wide 100% production statement and branch coverage gates
- Added explicit regression cases for control characters, out-of-range Unicode, list-form values, wildcards, no-op recovery, and recovered-validator installation before retained work starts
- Added deterministic recovery-before-wrapper and close-before-wrapper concurrency regressions that prevent temporally mixed public flush snapshots
- Extended isolated packed-artifact ESM, CommonJS, and strict TypeScript consumers to prove the durable session and strong-tag validator work without React, React DOM, TipTap, ProseMirror, or Yjs installed

### Documentation
- Added buyer-visible autosave onboarding, explicit `autosave` and `revision-evidence` distribution surfaces, and npm persistence discovery metadata without changing runtime behavior or package version
- Added buyer-visible autosave onboarding, explicit `autosave` and `revision-evidence` distribution surfaces, and npm persistence discovery metadata without changing the released package version
- Updated package discovery metadata to identify the server-validator-bound durable autosave session alongside the lower-level single-flight coordinator
- Corrected the autosave onboarding so initial and replacement validators are checked before use and come from the durable host's server-issued strong `ETag` rather than local revision evidence; missing, weak, or malformed validators now fail closed in the example
- Documented that host-owned save callbacks must apply their own timeout or abort signal because an unresolved callback intentionally retains the active single-flight operation; retry policy remains host-owned
- Added a deterministic repository contract test and APA 7th-style doctoring for README, npm-search, Node.js package-export discoverability, RFC 9110 validator ownership, and quoted opaque-tag syntax

- Added operator and doctoring evidence for the durable autosave session, coherent recovery-time flush snapshots, host ownership boundaries, exact-head verification, and acquisition-review scope

## [0.5.28] — 2026-08-05

Expand Down
91 changes: 91 additions & 0 deletions docs/doctoring/durable-autosave-session.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Doctoring record: Durable autosave validator session

**Date:** 2026-08-05
**Target release:** Unreleased after Inkspan 0.5.28
**Decision owner:** ContextualWisdomLab
**Scope:** Provider-neutral handoff of server-issued strong entity tags across the framework-independent single-flight autosave queue.

## Buyer-visible gap

Inkspan 0.5.28 supplies deterministic revision evidence and a bounded single-flight autosave queue. A host nevertheless has to retain a mutable durable `ETag` beside the queue, put the current value into every `If-Match` request, validate the server's replacement value, advance it only after a committed write, and replace it correctly after conflict recovery. That repeated closure is security-sensitive integration code. A missing, weak, malformed, stale, or locally derived validator can defeat lost-update protection even when the queue itself remains correctly ordered.

The selected bounded slice adds a durable autosave session to the existing framework-independent package. It makes the required concurrency base explicit in every host callback and owns only the local validator handoff. This reduces buyer integration risk without moving transport, authorization, tenant isolation, persistence, credentials, migration, retention, audit, retry, idempotency, conflict UX, or model-use policy into Inkspan.

## Decision

`createDocumentAutosaveSession()` combines one existing single-flight queue with one server-issued strong entity tag. The initial tag must come from the durable representation loaded by the host. For each active revision the session invokes the host callback with a frozen request containing:

- detached, deeply frozen Inkspan document revision evidence; and
- the exact current `ifMatchStrongEntityTag` that the host must compare atomically in its authorized durable write transaction.

A committed callback result must contain `status: "saved"` and the server-selected `nextStrongEntityTag`. The session advances its durable base only after that exact result passes fail-closed validation. A conflict result does not alter the base. A malformed result, inaccessible reflection surface, promise-assimilation failure, rejected callback, or thrown callback blocks progression through the existing redacted queue failure path and leaves the previous durable tag intact.

`resume(nextStrongEntityTag)` is intentionally explicit. It succeeds only while the session is blocked and only after validating one replacement strong entity tag obtained through a host-owned authenticated reload, compare/merge/fork decision, or idempotency confirmation. The replacement is installed before retained work resumes, so the next callback cannot observe the stale pre-recovery base.

## HTTP validator boundary

RFC 9110 defines an entity tag as an opaque validator selected by the origin server. `If-Match` uses the strong comparison function and is commonly used with state-changing methods to prevent the lost-update problem. Inkspan's SHA-256 revision evidence is deterministic local equality evidence for its canonical envelope; it is not automatically the durable service's selected representation validator.

`isStrongHttpEntityTag()` therefore accepts exactly one quoted opaque tag matching the RFC 9110 character grammar and rejects weak `W/` tags, unquoted values, whitespace, controls, lists, wildcards, Unicode beyond the HTTP `obs-text` range, and any input requiring trimming or repair. Syntax validation does not establish authorization, freshness, tenant membership, or correspondence to a durable representation; the host remains responsible for all of those properties.

## Concurrency and failure invariants

1. Exactly one durable save callback can be active because the session delegates scheduling to the existing single-flight queue.
2. Every callback receives the durable tag current at callback start, not the local content revision and not a later mutable value.
3. The durable tag advances only after an exact valid `saved` result supplies a valid server-selected replacement tag.
4. Conflict, invalid result, reflection failure, promise-assimilation failure, and callback failure never advance the durable tag.
5. Blocked retained work cannot restart until the host supplies a valid recovered durable tag.
6. The recovered tag is installed before queue progression resumes.
7. Session snapshots are frozen and contain no document body, callback result, credential, tenant identifier, or original exception.
8. Public errors remain redacted and machine-readable through the existing `DocumentAutosaveQueueError` categories.
9. The runtime and declaration graph contain no React, React DOM, TipTap, ProseMirror, Yjs, DOM, provider SDK, storage driver, credential, or network client dependency.
10. No database object, migration, scheduler, model call, reviewer identity, or credential-chain change is introduced.
11. `flush()` re-reads the current queue state after each awaited terminal notification. If recovery or shutdown starts before the public wrapper continuation executes, it follows the new transition until the session is currently idle, blocked, or closed, then combines that lifecycle state and durable validator without another asynchronous boundary.

These invariants create a linearizable process-local handoff around host callbacks and public session snapshots. They do not create a distributed transaction. Durable correctness still requires the host to compare the supplied base and commit the new representation atomically inside the same authorized storage transaction.

## Security and privacy analysis

The session validates options and callback outcomes fail-closed. Callback result inspection uses exact own-property descriptors rather than ordinary status or tag getters. Promise assimilation is part of callback execution: a returned thenable whose `then` access throws is treated as an ambiguous host callback failure rather than as trustworthy result data. Reflection failures after successful assimilation are treated as invalid results. Neither path copies private values or exceptions into the public error.

Entity tags may correlate representations and must be treated as tenant-confidential metadata. They must not become public URLs, metric-label values, analytics dimensions, unauthenticated log fields, authorization tokens, or durable audit substitutes. The host applies its existing access control, encryption, retention, residency, redaction, and audit policy.

No database object is added. A host that persists session or document state must use database object names containing at least two descriptive words, with `snake_case` preferred unless the target ecosystem requires valid CamelCase or PascalCase.

## Modular ownership matrix

| Concern | Inkspan durable session | Host or CWL service |
| --- | --- | --- |
| Strong entity-tag syntax validation | Owns | Supplies server-selected values |
| One-active/one-pending local ordering | Owns | Chooses debounce and enqueue timing |
| Durable validator handoff between callbacks | Owns | Returns committed replacement tag |
| Transport and credentials | Does not own | Owns |
| Authentication and authorization | Does not own | Owns |
| Tenant isolation | Does not own | Owns |
| Atomic durable `If-Match` comparison and commit | Does not own | Owns |
| Persistence, migration, backup, and rollback | Does not own | Owns |
| Retention, residency, redaction, and audit storage | Does not own | Owns |
| Retry, offline, idempotency, and conflict UX | Does not own | Owns |
| Model provider and model-use policy | Does not own | Owns |

This boundary preserves standalone operation and allows naruon compose surfaces, `ui.panel`, contextual-orchestrator workflows, and other CWL hosts to supply their own authorized service adapters without importing host policy into the editor package.

## Verification evidence

Deterministic unit and integration tests cover strong-tag grammar, malformed options, hostile option getters, sequential server-validator handoff, frozen callback requests, conflict retention, explicit recovered-tag installation, invalid exact callback shapes, weak replacement validators, inaccessible reflection, promise-assimilation failure, thrown transport failure, flush, shutdown, and document-free snapshots.

Test-first microtask-ordering regressions prove that an internal blocked or idle notification cannot escape as a stale lifecycle snapshot after a request continuation has already installed a recovered validator, restarted retained work, or begun shutdown. The red recovery commit preserves the inconsistent `blocked` plus recovered-validator result; the implementation re-reads current queue state and waits for the resumed or closing transition before returning one coherent terminal snapshot.

The exact packed npm artifact is executed through ESM and CommonJS and compiled as a strict TypeScript consumer in an isolated temporary package tree that contains no React, React DOM, TipTap, ProseMirror, or Yjs installation. The packed tests prove that the exported session supplies the initial server tag, adopts only the returned replacement tag, preserves framework independence, and exposes complete declarations.

Repository acceptance remains 100% production statement, branch, function, and line coverage; TypeScript type checking; deterministic library and demo builds; isolated package-consumer verification; security and supply-chain scans; exact-current-head automated review; independent approval; and branch protection. A version bump, tag, registry publication, provenance, or immutable release publication is prohibited until all integrated release-acceptance gates pass on the exact merge candidate.

## References (APA 7th edition)

Fielding, R., Nottingham, M., & Reschke, J. (2022). *HTTP semantics* (RFC 9110). RFC Editor. https://doi.org/10.17487/RFC9110

Herlihy, M. P., & Wing, J. M. (1990). Linearizability: A correctness condition for concurrent objects. *ACM Transactions on Programming Languages and Systems, 12*(3), 463–492. https://doi.org/10.1145/78969.78972

International Organization for Standardization. (2023). *Systems and software engineering—Systems and software quality requirements and evaluation (SQuaRE)—Product quality model* (ISO/IEC 25010:2023). https://www.iso.org/standard/78176.html

Rundgren, A., Jordan, B., & Erdtman, S. (2020). *JSON Canonicalization Scheme (JCS)* (RFC 8785). RFC Editor. https://doi.org/10.17487/RFC8785
Loading
Loading