Commit Graph
15 Commits
Author SHA1 Message Date
Turtle 1bd3df926d fix(plugin-manager): wait for a pnpm run its exited operation left behind
Review found three ways the takeover could let two writers overlap.

- A claimant that read an exited PID could remove the lock of a new
  holder that reused that PID before the claim. The claimant now probes
  the PID again under the claim, after re-reading the record.
- The browser Worker's process shim reports its own PID as absent, so a
  second writer in the Worker took over the first one's live lock. A
  record naming the contender's own process is never taken over.
- Takeover proves only that the holder exited, while a pnpm run it
  started can still be writing the profile. Each profile operation now
  records its pnpm run in .plugin-manager/run.json and removes the record
  when the run ends; an operation that finds a record waits for the run
  within the existing tree bound and otherwise refuses, naming the
  process and the record, without running pnpm.

A failed removal of the exited holder's lock now waits instead of
failing the acquisition, the probe range is stated as int32, and the
tests inject exited PIDs instead of reusing real ones, which Windows
reissues at once.
2026-09-24 19:55:41 +08:00
Turtle 910711e6c1 fix(atomic-write): keep the PID-only lock record
The hostname guarded only writers on several hosts sharing one file,
which no supported deployment does, and macOS changes its hostname when
networks change, so locks from before the change could not be taken
over. The nonce closed a race that needs the PID space to wrap between
two reads. Keeping the `<pid>\n` record lets processes of both releases
interoperate.
2026-09-24 19:06:07 +08:00
Turtle 7e7ba139fd fix(atomic-write): take over a writer lock whose holder exited
A process that ends without running withFileLock's finally left
<file>.lock behind, and every later writer timed out until an operator
deleted it. `dsh plugin` interrupted with Ctrl-C, `dsh web` exiting
right after disposal while a cancelled install still restores files,
and any crash all left the profile package lock this way.

The lock now records its holder's PID, hostname, and a nonce. A
contender that finds a record from this host whose PID a signal probe
reports absent claims that record through a wx-created claim file,
re-reads the lock, removes it only while it still holds the record, and
retries at once. A PID-only record from an earlier release counts as
written on this host, so locks left before this change are taken over
too. Empty, incomplete, foreign-host, and live-holder records are still
waited for.
2026-09-24 18:46:42 +08:00
07akioni 9187c3662e Merge master and fix desktop editing and Windows lock contention 2026-09-14 12:16:33 +08:00
Turtle b8e3b32fcf Merge master into codex/omit-unneeded-invariants 2026-08-31 14:38:55 +08:00
Yichen Jiang 3e56eaaa0f fix(atomic-write): retry transient Windows replacement 2026-08-29 13:04:06 +08:00
Turtle 15f2997bcb cleanup: omit unneeded invariant companions 2026-08-28 13:12:52 +08:00
Yichen Jiang 26a8e6a555 feat(atomic-write): state the writer-lock wait limit per call
How long a contender waits is a property of the operation the lock holder
runs, not of the write protocol. The 2s default was sized for the
render-and-rename cycle every call site had; a credential mutation that
refreshes an expired token performs a network round trip while holding the
lock, and leaving the default in place would fail every other writer of that
file for the duration.

`withFileLock` takes an optional `waitMs`; the retry cadence stays fixed
because it governs how often a contender asks, which no caller varies. Every
existing call site keeps the default.
2026-08-20 17:58:38 +08:00
imccyu ef75b6ff2f perf(ci): parallelize coverage and web snapshots in-job 2026-08-18 21:15:20 +08:00
imccyu ec601ca13d build(vendor): rescope the vendored Cordis packages into @deepseek-ai
Machine-produced by `pnpm run rescope-vendor --apply` plus the regeneration it
prints: `pnpm install` for the lockfile, `pnpm run gen-third-party-notices`,
`verify-translation-pairing --write` for the touched bilingual pairs,
`gen-doc-graphs`, and one typert snapshot whose ids embed character offsets.
`pnpm run rescope-vendor --check` verifies the result.

Renames nine vendored packages (cordis, cosmokit, schemastery and the six
@cordisjs plugins) and every reference that resolves them: manifest names and
dependency keys, module specifiers including declare-module merges, cordis.yml
plugin names, tsconfig paths, every Markdown fence, and `docs/` prose.
Directory names, upstream versions, and dependency ranges are unchanged, so
vendor/README.md still reads as an upstream snapshot; its manifest table gains
an upstream-name column so THIRD_PARTY_NOTICES keeps MIT attribution pointed
at each fork's origin.

The tutorial tier follows the rename end to end: its yaml fences named plugins
the Loader can no longer resolve, its `ts ignore-check` fences disagreed with
the compiled fences beside them, and its prose quoted both. The contracts that
told readers to keep upstream names — the root convention and the vendoring
cookbook's tree comment and manifest invariant — now say to rescope instead.

Two rules read `@deepseek-ai/` as "another workspace plugin": the client bundle
purity gate now names the vendored libraries a browser bundle inlines, and the
files where a bare `cordis` is an agent-preset id keep that product data.
2026-08-10 22:04:13 +08:00
Tianyi Cui e9096c891c Merge latest master into worktree/pr884-simplify 2026-07-31 11:00:07 +08:00
Tianyi Cui f9f8148e79 refactor(credentials,llm): remove speculative mutation and route lifecycle 2026-07-31 01:27:46 +08:00
Yichen Jiang caf2db0929 Merge branch 'worktree-config-settings-seam' into worktree-llm-dynamic-config
# Conflicts:
#	packages/settings/settings-local/src/index.ts
2026-07-30 17:39:07 +08:00
Yichen Jiang 90c3118302 fix(credentials-local): one operation chain, read-modify-write under the shared writer lock, and a quote-aware line editor
Review round three, credentials half. dsh-atomic-write grows the
cross-process writer-lock primitive (withFileLock: wx sentinel, bounded
backoff, stale takeover via onStaleBreak, deadline failure) plus a dirMode
option, and settings-local migrates its private copy to it; both providers
now create harness-home directories 0700.

credentials-local reuses the reviewed settings-local shape: watcher
reloads and line edits share one settled operation chain; every write
re-reads the document under the lock and publishes unobserved external
entries before editing, so an edit inside the debounce window (or another
process's write) can never be overwritten; the watcher's ready signal
queues one reconcile closing the startup gap.

The line editor is now physical-line aware: continuation lines of a
quoted multi-line value are never mistaken for assignments, untouched
lines keep their exact bytes (CRLF included), an edited line keeps its
own terminator, and appends use the document's dominant ending. A
multi-line entry reports writable: false, matching what set() would do.

The Credentials base class owns a contained notifyUpdated fan-out:
providers publish only after the commit, every listener runs, sync throws
and async rejections are logged without failing the committed write, and
INVARIANT-coded failures rethrow after the fan-out.
2026-07-30 15:40:09 +08:00
Yichen Jiang ba37180946 feat(util): extract dsh-atomic-write and migrate settings-local writes
writeFileAtomic: exclusive-create random-suffix temp + rename carrying the
caller-stated mode; settings-local persistSection now consumes it. The
credentials-local store shares it next.
2026-07-29 12:59:41 +08:00