chore(storage,workspace): gates — coverage, catalogs, bilingual note

- 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.
This commit is contained in:
imccyu
2026-07-24 22:49:57 +08:00
parent 80b3b6d917
commit 0559e0207e
33 changed files with 1333 additions and 58 deletions

View File

@@ -16,3 +16,24 @@ Storage hub (`ctx.storage`) for non-session data: a named backend registry plus
| `dsh-storage-json` | JSON backend: one unit per human-readable file, atomic whole-file rewrite |
| `dsh-storage-sqlite` | SQLite backend: one database hosting all routed units, document-per-row |
| `dsh-domain` | Domain data form (`ctx.storage.domain`): typed schemas, write chain, change events |
## Model Experience
### Backend and form registrations
#### What the model sees
Nothing. `ctx.storage` is a host-side registration table; the hub registers no tools, injects no prompts, and writes no session events.
#### Token effect
Zero direct tokens on every request.
#### KV Cache effect
Independent of live requests: the hub never touches a request prefix, so it cannot invalidate provider cache reuse.
## Known Limitations and Deferred Work
- **`kv` is the only data shape** — the append-log facet the future session-backend migration needs is reserved in the design note but not yet defined; backends currently have exactly one facet to implement.
- **Forms resolve lazily** — reading `ctx.storage.domain` before the domain plugin mounts throws `form-not-mounted`; assemblies order plugins accordingly (misconfiguration fails loud rather than silently deferring).

View File

@@ -33,11 +33,33 @@ describe('Storage service', () => {
const facility = { marker: true }
const dispose = ctx.storage.mount('domain' as never, facility as never)
expect(ctx.storage.form('domain' as never)).toBe(facility)
expect(ctx.storage.domain).toBe(facility)
expect(() => ctx.storage.mount('domain' as never, facility as never)).toThrowMatchingObject({
code: 'duplicate-mount',
})
dispose()
expect(() => ctx.storage.form('domain' as never)).toThrowMatchingObject({ code: 'form-not-mounted' })
expect(() => ctx.storage.domain).toThrowMatchingObject({ code: 'form-not-mounted' })
})
it('ignores a stale disposer after dispose and re-mount / re-register', async () => {
const ctx = new Context()
await ctx.plugin(Storage)
const first = { first: true }
const second = { second: true }
const staleMount = ctx.storage.mount('domain' as never, first as never)
staleMount()
ctx.storage.mount('domain' as never, second as never)
staleMount()
expect(ctx.storage.form('domain' as never)).toBe(second)
const backendA = fakeBackend()
const backendB = fakeBackend()
const staleRegister = ctx.storage.backend.register('json', backendA)
staleRegister()
ctx.storage.backend.register('json', backendB)
staleRegister()
expect(ctx.storage.backend.get('json')).toBe(backendB)
})
})