master added verify-dsh-package-licenses while this branch was open: every
repository-owned DSH package must declare "license": "MIT". This package
carried BSD-3-Clause from its creation, so the gate failed and took the
required "node 24 / static" lane down with it.
Resolutions:
- docs/module-graph.md, docs/config-catalog.md: generated files. Regenerated
with gen-module-graph and gen-config-catalog on the merged tree, then carried
the new package's entries into the Chinese sides and re-recorded both
pairings. Each side now differs from master by exactly the
code-runtime-python rows.
- scripts/verify-package-readme-model-experience.ts, tsconfig.host.json: master
renamed packages/bash -> packages/shell, packages/pty -> packages/terminal,
code-runtime-worker -> code-runtime-worker-thread and agent-tool-mode ->
agent-tool-presentation. Kept master's names and re-added this branch's
code-runtime-python entry.
Adapted the package to conventions master introduced while the branch was open:
version 0.1.0-rc.6 with publishConfig.access "public" (the release-member rule
check-workspace-constraints now enforces), the invariants project reference
moved to packages/runtime-diagnostics/invariants, and the README companion link
retargeted to code-runtime-worker-thread.
Every release member now declares publishConfig.access: public, so the scope no
longer mixes levels: the 221 packages/*/* and apps/* manifests join the vendored
framework and the native packages.
check-workspace-constraints drops the per-sequence expectation and holds every
release member to public, which is what stops a member from drifting back.
Access is a property of the package, not of a version: the dsh packages already
published as restricted become world-readable at their next publication.
master cut 0.0.1-rc.2 while this branch was open. The version bump touched
every existing package but not this new one, so check-workspace-constraints
rejected the mismatch and took the required "all checks passed" job down
with it.
Resolutions:
- docs/module-graph.md, docs/config-catalog.md: both are generated files.
Regenerated with gen-module-graph and gen-config-catalog on the merged
tree instead of hand-merging the conflict hunks.
- scripts/verify-package-readme-model-experience.ts: both sides appended
registry entries; kept all four.
Adapted this package to conventions master introduced while the branch was
open: version 0.0.1-rc.1 with publishConfig and repository metadata, the
workspace: protocol for peer dependencies, and the @deepseek-ai/cordis
rescope in package.json and src/invariant.ts.
Two comments described facts that belong to later layers of the stack:
- The workspace-constraints whitelist comment described a bootstrap the host
spawns by path. This layer's py/ holds only protocol.py, the wire-vocabulary
mirror, and nothing here spawns it. State what the whitelist entry actually
covers: the Python source ships as-is rather than built.
- checkDoneValue's JSDoc claimed maxValueBytes "defaults to 32 KiB". This
package defines no config and no default; maxValueBytes is a required boot
frame field. Name it as the budget instead, so the prose cannot drift when
the owning implementation picks a default.
Comment-only; the bound argument is unchanged.
1504 hand-written ranges pointing at workspace members become workspace:^, so
pnpm pack substitutes each member's real version at publication: sibling
peerDependencies follow the family version instead of being pinned at ^0.0.1,
and a reference to a vendored package follows that package's own line. Without
this, publishing 0.0.2 ships peer ranges naming a version that does not exist,
and 0.0.1-rc.1 does not satisfy ^0.0.1 either.
It also retires ranges that had gone stale against the workspace: ^4.0.0-rc.6
for a 4.0.0-rc.7 checkout, ^3.17.0 for schemastery 3.18.0.
workspace:* stays where an exact published version is the point, which is how
the Landlock entry pins its platform packages.
A workspace constraint now requires the protocol, so a new package cannot
reintroduce a hand-written range. The same constraint caught packages/boot/cmdline
arriving on master without the publishable trio, which this change completes.
Every package under packages/, apps/, and vendor/ drops "private": true and
declares publishConfig.access "restricted": the repository now states which
packages it publishes instead of deciding it at publish time. Each one also
declares its repository and directory, which is how a consumer of a private
package reaches its source.
The Landlock packages move to restricted with them. They have never been
published, so nothing anonymous depends on them today, and the whole
@deepseek-ai scope stays private.
The workspace constraint that required every package to be private now applies
to non-members only, and asserts the publishable trio on each release member.
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.
Delete design-session citations (decision/audit/plan ordinals, stack
positions), change narration, review choreography, and reviewer-addressed
justification from comments, JSDoc, docs, READMEs, Agent Notes, tests, and
generator templates; restate every affected fact as current-state contract
prose. Fix generated docs at their sources and regenerate the catalogs and
cordis-surface regions; re-paste type-equiv blocks; update every bilingual
counterpart and re-record the pairs. Record the citation rule in the
committed-artifact-citations Agent Note.
core.md read as a type grab-bag: LLM wire vocabulary up front, the agent/loop story buried, and no correspondence to packages/core. It now opens on the packages/core control spine — the package-by-package loop map with a Page column into session/system-prompt/tools/scope — and keeps only what the spine group declares plus the repo-wide patterns: the Agent handle with its delivery/cancellation/interception contracts, the SessionEvent envelope, branded ids, the …Map pattern. The conversation vocabulary (Message/ContentBlock, the model request, adapters — 17 type-equiv blocks) moves to llm-streaming.md, which now declares packages/llm end-to-end; the duplicate ContentBlockMap paste near its seam section folds into the moved section, and the manifest, LINK_MAP, README table rows, website label (Core data structures → Core), and inbound anchors follow.
Every packages/<group>/README pair is now a thin front door in one shape: a why-first intro (bash's seam-pattern-first paragraph rewritten as 'shell execution for the agent'), the package table, and a closing pointer to the owning docs/subsystems page — the bash-style table stays the load-bearing middle. Load-bearing trailing paragraphs relocate rather than vanish: the fs no-timeout rationale becomes a filesystem.md section (both languages), session's four sectioned tables merge into one 12-row table, examples' legacy-bin H2 collapses to a pointer at jsonrpc-demo's README, and design rationale that already lives in an Agent Note or subsystem page is now linked instead of restated. All 40 pair records re-recorded.
Rename UnionCoversRoster/RosterCoversUnion to UnionSubsetOfRoster/RosterSubsetOfUnion so
the names read in the same direction as their extends clauses, share the python3 -I -B
flags between the two mirror probes, and align the README and Agent Note prose with the
checkDoneValue JSDoc: the escaped-size scan is the metering itself, not deferred work.
Regenerate docs/module-graph.md, which listed code-runtime-python twice.
- Drop the review-history narrative from checkDoneValue's JSDoc (the "in the
previous implementation … now avoids" clause); state the current contract only.
- Assert encodeJsonPlain on the same 100k-deep value the metering test uses:
its headline contract is stack-safety (JSON.stringify would throw), but no
test exercised the encoder on a deep value.
Address the latest review round:
- WireFrameShapesCoverUnions checked only union ⊆ roster, so removing a frame
from a message union (e.g. dropping ReplyErr from ReplyMessage) left the check
true while the public TS union diverged from the wire. Replace it with a
bidirectional equivalence between MessageFrames and the roster's message-frame
value types (nested Namespace/ErrorClass/DoneErrorField excluded): both a frame
added to a union without a roster entry and a frame removed from a union now
fail typecheck (both verified).
- The mirror e2e's python3 probes imported protocol.py without -B, writing
py/__pycache__/*.pyc into the (un-ignored) source tree. Add -B to both.
- Refresh the metering prose (checkDoneValue JSDoc + README both sides + Agent
Note both sides): the incremental-work list no longer says "per-key
JSON.stringify" now that jsonStringBytesUpTo scans without stringifying;
re-record the README and Agent Note i18n pairings.
Two review findings on checkDoneValue's metering and the wire-mirror binding:
- The string/key byte check used a decoded-length lower bound and then called
JSON.stringify, which materializes the ~6x escaped copy before the over-budget
check — the hundreds-of-MB spike the metered walk exists to avoid. Add
jsonStringBytesUpTo, a non-allocating scan that computes the exact escaped
UTF-8 size (matching JSON.stringify byte for byte, including surrogate pairs
vs lone surrogates) and bails the instant it crosses the remaining budget; use
it for both string values and object keys.
- The frame roster in WIRE_FRAME_FIELD_ROLES was hand-written, so a frame added
to ChildToHost/ReplyMessage without a roster entry slipped past. Introduce
WireFrameShapes (name -> interface) as the canonical roster the roles map is
bound against, plus a WireFrameShapesCoverUnions compile-time assertion that
every message-union member appears in it (verified: adding a frame to a union
without a WireFrameShapes entry fails typecheck).
Address the remaining review findings on the wire-mirror layer:
- Export PROTOCOL_FD from protocol.ts as the TS-side source of truth the host
wires, and assert the Python constant against it in the mirror e2e instead of
a bare literal 3, so an fd drift on either side is caught.
- Correct the FrameFieldRoles JSDoc to point at the actual assertion site
(WIRE_FRAME_FIELD_ROLES's satisfies clause, not WIRE_FRAME_FIELDS).
- Drop the redundant explicit type annotation on WIRE_FRAME_FIELDS (the trailing
`as` cast already types it; Object.fromEntries returns an index signature).
- Refresh the mirror-test comment to describe the roles-map binding (a TS-side
add/remove/rename/optionality-flip fails typecheck; a Python-side change fails
the comparison).
The array-based FrameFields<T> only checked that listed names were members of
the frame's keys, so a field added to a TS interface (e.g. LogMessage.seq?)
left the existing arrays a valid subset — typecheck passed, and since the
constant and Python both lacked the field the mirror test passed too. The
JSDoc's claim that runtime covered this was false.
Replace it with WIRE_FRAME_FIELD_ROLES, a per-frame map keyed by field name
(`Record<RequiredKeys<T>, 'required'> & Record<OptionalKeys<T>, 'optional'>`),
so every interface key MUST appear with a matching required/optional tag: an
added field, a removed field, a rename, or an optionality flip all fail
typecheck at the roles map (verified). WIRE_FRAME_FIELDS is projected from it
as the sorted arrays the mirror test still compares to the Python TypedDicts.
Also drop the `export` added to the promoted frame interfaces (Namespace,
ErrorClass, RunMessage, DoneErrorField, ReplyOk, ReplyErr) — nothing outside
protocol.ts imports them, so the barrel surface is unchanged and knip stays
clean.
The previous mirror binding (FrameFields<keyof T>) only checked membership: it
could not see a TS-side optionality flip (truncated? -> truncated leaves keyof
unchanged) or a field added on one side, so the "depends on the TS
declaration" claim was overstated.
- Promote the inline frame shapes (Namespace, ErrorClass, DoneErrorField,
RunMessage, and the two Reply variants) to named interfaces so every frame
binds uniformly.
- Derive FrameFields from RequiredKeys<T>/OptionalKeys<T>, so `required` and
`optional` each accept only that side's keys. An optionality flip or a rename
now fails typecheck (verified: flipping LogMessage.truncated to required
errors at the constant).
- Enumerate EVERY public TypedDict in py/protocol.py in the mirror e2e (not a
name list taken from the TS side) and assert both the frame roster and each
frame's required/optional sets by exact equality, so a frame or field present
on only one side of the wire fails the test.
Two gaps from the previous round's fixes:
- checkDoneValue flagged a non-lossless number but skipped counting its encoded
bytes, so a value over budget ONLY through that number classified as
non-lossless instead of over-budget (e.g. [Infinity] at cap 3, whose encoding
is 10 bytes). Count the scalar's bytes even when flagging, so the budget check
wins as the JSDoc promises. Add cap-3 regression cases.
- The mirror e2e compared the Python TypedDict keys against a hand-written
constant, so a field change on the TS side alone would not fail it, and the
reply frames were not probed at all. Introduce WIRE_FRAME_FIELDS in
protocol.ts, bound to each frame interface's key set via `satisfies` (a
renamed/removed field breaks typecheck — verified), and drive the mirror test
from it, now covering ReplyOk/ReplyErr too. The test therefore fails on
one-sided drift from either language.
Address the two standing review suggestions in this layer rather than deferring
them to PR #4:
- Extend tests/protocol-mirror.e2e.ts to read each py/protocol.py TypedDict's
required/optional key set and assert it against the wire field names
src/protocol.ts declares (global included, via functional TypedDict). The
round-12 class of drift — a renamed/dropped field, or one side making a field
optional the other requires — now fails a test instead of relying on review.
Field types remain review-guarded (no mechanical TS/Python equivalent).
- Drop the forward references to PR #4's internal mechanisms from this layer's
prose: the "256 MiB frame ceiling" figure and the "(index.ts)" fd-3 pinning
citation become an abstract "host-side inbound frame-size cap" so the JSDoc,
spec, README, and Agent Note describe only what this layer owns.
Update both README sides and the Agent Note (both languages) to state the
mirror is now executable, and re-record their i18n pairings.
checkDoneValue returned non-lossless the instant it hit a non-finite/negative-
zero number, before finishing the budget metering. A value that is BOTH over-
budget and non-lossless then classified by member order: `["<huge>", 1e400]`
gave non-lossless while `[1e400, "<huge>"]` gave over-budget — the same value,
two verdicts — which would drive the consumer to emit invalid-output vs
output-limit non-deterministically, contradicting the JSDoc promise that an
over-budget value is rejected as over-budget regardless. Record the number
violation in a flag and let metering finish; return non-lossless only once the
whole value is confirmed within budget. Add a regression test asserting both
member orders classify as over-budget.
Both README sides still described checkDoneValue as "one bounded traversal /
一次有界遍历" — the same overclaim already retracted in the code JSDoc and the
Agent Note. Reword both to match: the walk bounds only the incremental
allocation it adds (escaped-string copy, enqueued children, per-key stringify);
the frame's own width is parsed upstream and capped by the host's fd-3 receive
buffer, not re-bounded here. Re-record README.i18n.yaml.
- Replace four raw U+0000 bytes in protocol.spec.ts string literals with the
\0 escape so the source stays plain text (a bare NUL makes text tools treat
the file as binary); the runtime value is unchanged, so the bytes:8 NUL-escape
assertion still holds.
- Sync the Agent Note (both languages) with the corrected checkDoneValue
contract: the walk bounds only the incremental allocation it would add, not
the frame width, which is already parsed and capped upstream by the host's
fd-3 receive buffer. Drop the "prevents a hundreds-of-MB allocation" overclaim
that the code JSDoc already retracted. Re-record the note i18n pairing.
ownValues' JSDoc claimed the generator avoids a "second full-breadth
allocation before a single value is examined", but for...in still materializes
the key-name enumeration when the loop starts — the same JS limitation the
checkDoneValue rewrite now acknowledges. What the generator genuinely saves is
the extra VALUE array Object.values/Object.entries would copy; state that
precisely rather than implying sublinear startup.
checkDoneValue cannot bound object width sublinearly: JS has no lazy own-key
iterator (for...in materializes the key set), and done.value is already
JSON.parse'd before the check runs, so the frame's width is paid upstream. The
genuine width bound is the host's fixed 256 MiB fd-3 receive buffer (a later
stack layer). Reword the JSDoc and branch comments to claim only what holds —
the traversal caps the INCREMENTAL allocation the check would add (escaped
strings, enqueued children, per-key stringify) and refuses over-budget before
those secondary allocations — and drop the mid-count micro-check that JS cannot
honor. Replace the Proxy test (whose ownKeys allocated a 2M array, proving
nothing) with assertions that an over-budget string/array/object is refused
before its escaped copy or child enqueue.
The base seam branch resolved its DUNDER_MEMBER inconsistency by keeping
/^__.+__$/ and asserting `____` (empty middle between two `__` pairs) does not
match. Drop this branch's earlier /^__.*__$/ stopgap so the seam file is
byte-identical to its base: the earlier change only existed because the base
was self-inconsistent, and the base now owns a coherent decision.
The object branch counted every own key before applying the size bound, so a
forged done.value with millions of keys and a small cap forced an O(frame)
walk — contradicting the O(cap) guarantee the comment promised and able to
block the host event loop. Bail mid-count the instant the running minimum
encoding (braces + 4 bytes/entry + commas) crosses maxBytes, and drop the now
-redundant post-count check the loop subsumes. Add a Proxy-based test proving
a 2M-key object enumerates fewer than 1000 keys under a 64-byte cap.
Also correct the checkDoneValue JSDoc: per-scalar byte length is measured via
scalarJson (exact BigInt digits for beyond-safe integers), not JSON.stringify.
- Translate README.zh.md's Model Experience body and KV Cache line, which
were left verbatim in English.
- Convert half-width punctuation to full-width across the README.zh.md
Known Limitations bullets and the entire Agent Note Chinese side, per
docs/i18n translation-rules.md Typography (MUST use ,。:()in Chinese prose).
- Re-record both README and Agent Note i18n.yaml pairing hashes.
- Reword the workspace-constraints extra-files comment: this layer's py/
ships only the wire-protocol mirror; the spawned bootstrap arrives later.