The bare 'domain' name was too generic for a published package. The
directory moves to packages/storage/storage-domain, the package becomes
@deepseek-ai/dsh-storage-domain, and the plugin/invariant names follow;
the ctx surface (ctx.storage.domain), the domain/changed event, and all
runtime behavior are unchanged. References, catalogs, graphs, and the
bilingual design note move together.
- domain/changed emission is isolated from the write path: an observer
throwing synchronously can no longer turn a committed (durable +
in-memory) write into a rejection; the failure is logged and later
writes proceed.
- Domain lifecycle belongs to the opening consumer: Domain gains an
idempotent close() (drain, unit close, reservation release), the
facility stops registering effects on its own context and instead
closes any still-open domains on unmount; WorkspaceRegistry holds its
domain through its own effect, so disposing and re-mounting the
consumer no longer wedges on already-open.
- defineDomain rejects a global schema accepting null at declaration
time: JSON null is the medium's absence sentinel, so a nullable global
could never round-trip; failing loud at the spec keeps set(null)
unrepresentable.
Regression tests cover all three (hostile listener, close/reopen and
consumer re-mount, nullable-global rejection).
- Per-file 100% coverage across the five new packages (invariant
companion suites, failure-injection negatives, lifecycle and
malformed-medium branches).
- Canonical README Model Experience / Known Limitations sections; new
storage/ and workspace/ group READMEs; packages/README.md rows (budget
ceiling raised 760 → 790 for the two new groups).
- Cordis catalog/type-link registrations, service-role classification,
and regenerated catalogs/graphs for the new services and events.
- Agent Note: English body + i18n pairing record; design-sketch fences
opted out of doc-typecheck as ignore-check.
- Two exactOptionalPropertyTypes/discriminant fixes in new tests.
doc-sync (24 gates), typecheck, hygiene, and the five-package suite
(92 tests) all pass.
Review findings applied across the group:
- storage hub: stale disposers no longer remove a successor registration;
the package now default-exports the Storage service class per the
service-package export shape.
- json backend: failed publishes roll back the authoritative memory state
(a rejected write can no longer resurface via get() or ride the next
publish); close() drains in-flight writes and blocks in-flight opens;
double-open rejects as a plain caller error instead of malformed-medium.
- sqlite backend: loadAll builds records on a null prototype (__proto__
keys round-trip instead of polluting), user_version is stamped only
after the schema is fully created, and corrupt record JSON rejects as
malformed-medium instead of a bare SyntaxError.
- domain form: writes persist before mutating authoritative memory or
emitting; DomainChanged is a put/deleted discriminated union.
- workspace: attach/detach idempotence decided on the write chain (stale
snapshots no longer short-circuit), create() requires a directory, and
startup fails loud on duplicate stored paths.
Eleven regression tests pin the fixed behaviors.
ctx.workspace registry owns WorkspaceId-branded records: realpath-
normalized unique paths (create rejects collisions; resolveByPath shares
the normalization), ordered sessionIds as the single source of ownership
truth, attachSession gated on the session header cwd matching the
workspace path (double-booking structurally impossible), dead session
ids filtered on projection and pruned on the next mutate, status()
reporting missing directories. No delete surface this phase — deletion
ships together with the session-side primitives as future work.