Delete @deepseek-ai/dsh-invariants, every package's ./invariant companion
and its tests, the invariant gates and Vitest setup, the scoped-event
resolver generator, and the INVARIANT-coded listener rethrow paths.
Update docs, READMEs, Agent Notes, and add an upgrade guide.
* feat(i18n): key translation pairing records by heading section
Replace the whole-file blob hashes in *.i18n.yaml with one entry per
heading section that still holds language-specific content, keyed by the
English heading-slug path. Blocks identical across the pair (generated
table rows, code blocks, shared regions) stay out of the hash, so edits to
different sections and identical regenerations merge with Git's default
text merge, including on GitHub.
Remove the dsh-translation-pairing merge driver, its resolver command and
installer wiring; gen-translation-brief recovers last-confirmed text from
commit history. Migrate all active records, archive the superseded Agent
Note, and record the replayed conflict reduction in the new one.
* test(i18n): refresh translation prompt snapshot for the terminology note
* feat(i18n): exclude only code blocks and generated regions from pairing records
Replace byte-equality masking with an explicit rule: a record hash skips
fenced code blocks and BEGIN/END GENERATED regions, the two kinds of
content the pairing gate already requires to be identical on both sides.
Move generated data into regions written into both pages:
gen-config-catalog renders both languages and the record with per-package
regions labelled by code identifiers; gen-module-graph keeps its table in a
region; gen-doc-graphs splices the event matrix regions into the Chinese
page. Add a generic region splice beside the region grammar.
- dsh-authorization's manifest becomes a release member on current
master's terms: version matches the root 0.1.0-rc.6 and
publishConfig.access is public, which the constraints gate and the
tarball pack's version verify both enforce after the master merge.
- Regenerate docs/module-graph (zh mirror included) for the
authorization package and the llm-pi-ai -> authorization edge; the
graph gate lives outside doc-sync and was never regenerated when the
package was added.
- The built-bin smoke seeds the versioned credentials document; this
branch's provider refuses the pre-release flat layout by design, and
the master-side test still wrote the old shape.
Review findings on #2509, all confirmed:
- Every writer of .credentials.yaml now waits out the record-mutation
lock (DOCUMENT_LOCK_WAIT_MS): refs and records share one file and one
lock, so a reference write or record delete contending with an OAuth
refresh must not fail at the 2s file-work default.
- api-key records are admitted before they are rendered: an empty key,
a non-POSIX env name, or an empty env value is refused at the write
instead of persisting a document the next boot rejects wholesale.
- llm-pi-ai no longer lets the credential-key grammar reject legal
route ids: reads answer "nothing stored" via isCredentialKeySegment
(new dsh-credentials export), deletes have nothing to remove, and only
a write refuses, as LlmError UNSTORABLE_PROVIDER_ID; flow registration
skips a future catalog id outside the grammar instead of failing the
mount.
- authorization/settled fans out with contained listener failures on
the credentials seam's terms (INVARIANT still rethrows), so a broken
watcher can never turn a finished attempt into a failure.
- notify() is fire-and-forget at the seam: a surface that cannot render
a notice loses the notice, never the attempt.
- A declined prompt is an outcome: interactions reject with the new
AuthorizationDeclinedError and the attempt settles cancelled instead
of failed.
- NOT_COMMITTED now confirms a commit observed during the attempt
(credentials/record-updated for the flow's key), so a re-auth cannot
pass a stale record off as fresh; a flow that deletes its record is
refused on the same code.
READMEs, the subsystem/event/config catalogs, and the Agent Note follow
the shipped behavior; memory.ts carries the dedup TODO.
pi-ai's auth model reaches this adapter through three translations, all
of which live here: a CredentialStore over the harness credential
records, an AuthContext over the credential plane and the host
filesystem, and one authorization flow per installed provider that
ships a login. The seams they consume name nothing from pi-ai, so a
second adapter family can arrive with a different auth model and share
them.
Every collection is now built with the store and the context rather
than with nothing, which is what makes a signed-in provider stay signed
in across the collection rebuild a configuration change causes. With a
posture that works, the configurable-provider directory no longer
withholds OAuth-only routes and `openai-codex` is offered again; the
predicate that withheld it is gone.
The credential plane stays optional. Reads answer "nothing stored"
without a credentials service because such a composition genuinely
holds no credential, while writes refuse by name — a login whose grant
evaporated would report success and then fail every request. Flow
registration is scoped to the authorization seam, so a headless or ACP
composition mounts with no sign-in and everything else unchanged.
Two fixes found while wiring this up: pre-release credential fixtures
in the llm suites still used the flat document the record work
replaced, and a flow that ignores its cancellation signal would have
held its key for the life of the process — withdrawal now settles the
attempt either way.
Some credentials cannot be configured, only obtained: getting one means
a conversation — open this page, paste that code, pick an account. The
new seam owns that conversation and the one-attempt-per-key lifecycle,
and never the protocol, so a second authorization protocol arrives as
another flow rather than as another seam.
A flow is registered under the CredentialKey it writes, which is also
how the seam knows which plugin answers for the format inside that
record. The flow owns the write: run() resolving means the record is
already committed through ctx.credentials, and the seam confirms it.
That keeps a library persisting through its own store adapter the
single writer instead of being copied back out and written twice.
The interaction travels with the request rather than a registry,
because whoever starts an authorization is the one who can talk to the
human about it. A request already withdrawn never claims the key and
never starts the flow — relying on each flow to check its signal before
the first await would let one that does not hang holding the key.