Review-round hardening of canonical admission:
- passthrough now requires a single-frame source free of EXIF/XMP/IPTC
metadata, so location/device metadata never enters durable storage and
stored dimensions always describe the perceived pixels; animated WebP joins
GIF on the always-re-encode path (first frame only)
- SourceImageInfo records orientation-applied dimensions, keeping source and
stored raster on shared axes for coordinate mapping
- validateImage runs a canonical-encoding dry run, so a validated batch can no
longer be refused mid-write by the byte target (no partial writes)
- read_image names per-axis multipliers when rounding splits the two ratios
and maps IMAGE_TOO_LARGE to actionable downscale guidance
READMEs (both languages) describe the wide source envelope, the canonical
encoding and its fixed encoder parameters, and read_image's downscale
envelope; tool/config catalogs regenerate for the new schema and Config
fields. The read-image-dimension scenario now pins the acceptance the old
2000px admission cap refused: the 2001x1 source is admitted and stored
byte-identically, so the fixture stays platform-independent.
The canonical ref serializes mediaType, width, height, bytes in the order the
pre-canonicalization store used, so existing session-log fixtures and logged
histories keep byte-identical reference JSON.
When the attachment store's canonical encoding shrinks the file on disk, the
read_image envelope names the original dimensions and the multiplier that
maps coordinates measured on the attached image back onto the file, and the
output schema carries sourceWidth/sourceHeight for programmatic callers.
Admission now validates a wide source envelope (32MiB, 100MP, 16384px per
side) and persists a canonical encoding instead of refusing large sources:
EXIF orientation baked in, metadata stripped, long edge downscaled to the
configured canonical target (default 2048px), PNG palette for alpha/PNG/GIF
sources and a fixed JPEG quality ladder (85/75/60/45) until the canonical
byte target holds (default 1MiB). In-budget PNG/JPEG/WebP passes through
byte-identically so equal sources keep deduplicating to the same content
address; GIF always re-encodes to the PNG of its first frame, pinning the
first-frame meaning providers apply. Encoder parameters are fixed by design;
only the canonical budget is deployment configuration.
AttachmentStore.saveImage now resolves SavedImageAttachment: the durable
reference paired with the submitted raster's intrinsic facts, so a store may
persist a canonical re-encoding while callers keep the source dimensions for
coordinate mapping. saveImages keeps returning refs; every fake store and the
cordis API catalog follow the new signature.
Exposed by this branch touching rescope-vendor.ts, which runs the full
rescope check: the knip-logger-console exact edit targeted the
packages/util/home knip section that #2758 deleted (drop the edit), the
zh vendoring-cookbook anchor predates the rescope.zh.md link
localization (follow it), and the new shipped-root.spec.ts joins the
files whose bare 'cordis' tokens are preset ids.
The files constraint tables gained per-package expectations on master
while this branch changed two files lists: apps/cli no longer ships
config/, and dsh-agent-presets ships presets/ (ordered where the
expected-files derivation places extras).
The documentation site deployed on every master push, with no reviewer and
no version check, while npm, PyPI, and the public source repository all
advance only at a release tag. The Pages site is reachable without
authentication, so a merge published documentation ahead of every artifact
readers could obtain.
docs-pages.yml now declares workflow_dispatch alone and verifies the ref
through the gate npm publication already runs, so the site and the npm
sequence share one definition of a released version.
Review asked why the launcher special-cases one plugin's row. It no
longer does: the four shipped compositions move into the package
(presets/, in files), dsh-agent-presets resolves its own shipped root
and prepends it before configured roots (includeShippedRoot, default
true, opt-out for bare-machinery embedders), and the per-composition
derived patch, its spec, and the dump layer are deleted — profile-boot
and dump-config return to plain layer stacking. The always-load
guarantee now rides the schema default instead of patch ordering, so a
whole-config replacement keeps the shipped set and the squash, reload
freeze, and dump divergence stop being possible.
Gate globs, the web scaffold, and both preset browser lanes drop their
hand-fed shipped roots; the roster e2e keeps asserting configured roots
beside the shipped four against the built lib.
Fixes#2863.
python-release.yml only triggers on workflow_dispatch, so build.if:
github.event_name == 'workflow_dispatch' is always true and redundant; remove it
(the exact event set is already pinned in the spec). Update the spec assertion
accordingly.
Address PR #2875 review:
- Re-record python/development.i18n.yaml (corpus verify-translation-pairing was
out of sync after editing development.md/zh.md) and the 2026-08-11
python-publication-workflow pair after the dry-run wording tweak.
- Tighten the python-release spec assertion to the exact event set
(['workflow_dispatch']) instead of not.toHaveProperty('pull_request').
- Fix the 'dry-run run' wording in development.md and the note.
Corpus-wide verify-translation-pairing (1001 pairs) and note-format (594) pass;
ci-workflow.spec.ts 14/14.
The static gate (translation pairing) failed on a pre-existing master note:
2026-08-20-composer-edit-range-from-selection.zh.md:17 linked the zh target with
the en .md path. Point it at the .zh.md target and re-record the i18n hash. This
unblocks the required node 24 / static gate (it is not part of the python-release
gray-check change but sits on the same PR's CI path).
Review follow-ups: enumerate the derived shipped agent-preset root in
apps/cli README/reference dumps and the profile-boot module JSDoc
(bilingual pairs re-recorded), correct the stale AppCLIEntry/distIndex
analogy in the web scaffold and the shipped-root cross-reference in the
web preset e2e, drop the write-only ComposedProfile.rows field, and make
the Agent Note describe the dump path as sharing the derivation rather
than the builder.
Remove the pull_request:[labeled] trigger from python-release.yml so the
workflow no longer fires (and shows a gray skipped check) when a PR gets any
non-dry-run label. The credential-free dry-run validation is now manual-only
(workflow_dispatch with publish=false), preserving the validation capability
without a PR gray segment.
- python-release.yml: on is workflow_dispatch only; build.if is
github.event_name == 'workflow_dispatch'.
- ci-workflow.spec.ts: assert python-release has no pull_request event and the
simplified build.if.
- python/development.(md,zh.md) and 2026-08-11-python-publication-workflow note
(en/zh/i18n): describe the manual dispatch-only dry-run path.
Verification: ci-workflow.spec.ts 14/14, typecheck clean, note-format 585,
verify-translation-pairing consistent.
The boot-time agent-presets overlay replaced the composed roots with the
shipped root alone, so roots configured in a profile's cordis.patch.yml
vanished from the roster (externally reported in
deepseek-ai/deepseek-harness#3636). The overlay also froze the row's
boot-time config above every live reload and never reached the config
dump, which therefore showed roots the boot dropped.
Derive the roster patch from the current layers instead: prepend the
shipped root (system trust, wins duplicate ids) to configured roots,
share one builder across boot, live user-layer reloads, and
--dump-config, and fail loud on a roots value the launcher cannot
statically rewrite.
Fixes#2863.