Skip to content

refactor: lay out both halves in FSD and share the /auth contract across them - #113

Merged
TecFancy merged 131 commits into
mainfrom
development
Oct 16, 2026
Merged

TecFancy merged 131 commits into
mainfrom
development

Conversation

@TecFancy

@TecFancy TecFancy commented Oct 16, 2026 •

Copy link
Copy Markdown
Owner

Promotes development to main. No user-facing behaviour changes: routes, cookies, pages and config are the same.

What changes

  • D30, FSD layout. The host half moves into src/host/{shared,entities,features} and the client half into src/client/{shared,entities,features,widgets}. Both halves are checked by Steiger (npm run lint:fsd). Integration tests move to test/integration/. Client test declarations no longer ship in lib/, and lib/ is emptied before each build.

  • D31, cross-half contract. The /auth/* paths, the password-changed notice key, the login/logout URL builders and the endpoint payload types are defined once in src/protocol/. Each half reaches the contract through a single bridge, and slice:check enforces that. More than twenty route literals and the client's hand-written payload mirror are gone.

  • npm run host:check. A new gate covering what the dsh host relies on and no type checker sees:

    • package name, Loader row, client registration id and host name agree;
    • every manifest target exists;
    • the client bundle only requires modules the shell seeds;
    • there is no route literal outside src/protocol, every protocol path is registered, and every README route exists.

    --self-test rejects 18 broken trees. The gate runs in verify, in the CI build job and in run-gates.

  • The FSD decision is renumbered D29 → D30, because the cross-site bounce (fix(gate): bounce cross-site navigations so dsh's Strict cookie is sent #111) shipped first as D29.

Release

The squash title is refactor:, which release-please does not release on its own. When merging I will add a Release-As: 0.17.4 footer so that this ships as a patch.

Verification

npm run verify passes locally: format, lint, no-emdash, slice, Steiger, lock, decisions, docs, readme parity, type-check, 1077 tests with coverage, build, bundle check and host:check.

A blank session (New Session, no input/response yet) has current set in the session snapshot AND a hidden session header, so both sign-out entries were suppressed there. The hero shell.overlay entry now shows when the current session is undefined OR still blank (byId[id].blank), hiding only for a real non-blank session (header button takes over).
Replace the stale sidebar-foot screenshots with two real captures from production: the blank new-session page (floating top-right sign-out, dsh workspace collapsed) and a real English conversation (sign-out right of the Session log). Update README.md and README.zh.md to show the new top-right placement; remove the obsolete logout-sidebar-*.png files.
dsh plugin add installs the package into the profile's node_modules; the dsh-auth bin is not added to PATH, so README and deployment docs now call it through the profile (pnpm --dir ... exec dsh-auth) and gain a Troubleshooting section. Fixes #44.
…sh-Proxy deny-list (#47)

* feat(proxy): authenticated local proxy bin

* feat(guard): X-Dsh-Proxy deny-list for host-native methods

* docs: local proxy and deny-list deployment sections (zh+en) with systemd example

* test(proxy): cover CLI parsing and upgrade header/token branches

* docs: README local-proxy section and English design doc
dsh only auto-discovers project skills from .agents/skills/ (and
.dsh/skills/); the skills/ directory was never picked up despite the
AGENTS.md claim. Move both skills there and fix the description.
- scripts/run-gates.mjs (shared from the framework): named scenarios
  derived from the verify chain; auto-detects the change surface and
  runs the minimal evidence set, fail-closed to the full set
- pre-push runs npm run gates -- --base @{u} instead of only type-check
- CI: split the monolithic check job into hygiene/quality/build
  parallel jobs (no path filters - full coverage always)
- docs/development.md: document gates and the new pre-push
# Conflicts:
#	README.zh.md
#	lib/client.js
#	lib/client.js.map
#	lib/client/index.d.ts
#	lib/client/index.d.ts.map
#	lib/proxy-headers.d.ts
#	lib/proxy-headers.d.ts.map
#	lib/proxy-headers.js
#	lib/proxy-headers.js.map
#	lib/proxy.d.ts.map
#	lib/proxy.js
#	lib/proxy.js.map
#	src/client/index.tsx
#	src/client/logout-action.test.tsx
#	src/proxy-headers.test.ts
#	src/proxy-headers.ts
#	src/proxy.ts
refactor: layered src with barrels + framework verify gates + decision records
)

* fix(token): bridge the dsh launch token after a token-mode sign-in

* fix(token): sanitize the bridged location, share the fallback rule, and lock the assembly seam

* test(token): close the coverage gaps the second review pass listed
The wrapServer disposer used to clear the route tables and write back a
snapshot taken at wrap time. Reloading dsh-auth-gate alone (a config
change in the Plugins page) therefore dropped routes other plugins had
registered after the wrap, resurrected routes whose owner had been
disposed, and made that owner's next load throw "duplicate exact route".

Unwrap now swaps an entry back to its original handler only while it
still holds a guard this wrap produced (tracked in per-wrap WeakMaps).
Deleted entries stay deleted, foreign replacements are left alone, and
post-wrap routes survive with the guard removed.

Adds a real cordis + webserver reload repro and per-entry unit tests,
records the change as decision D27 (superseding impl-m1 D6's snapshot
clause), and updates the code-review skill rule that asked for an exact
pre-wrap restore.
`engines.dsh` and the `@deepseek-ai/dsh-storage-domain` peer both end in
`>=0.2.0-rc.1` now; the three caret alternatives stay as the record of the
host lines that were actually verified. A hand-added alternative always
lags a dsh release, and a rejected bundle is skipped silently, so the login
gate would disappear rather than fail loudly. The guard test now asserts the
floor covers every later host version, and the readme, specs and D27 record
the trade-off. No source change (D27).
…rollback)

Brings in the guard unwrap rollback fix (#107) and the 0.17.1 release. That
fix's decision record already holds D27, so the corridor-floor record written
on 2026-10-07 is renumbered D28 and its references follow.
The host half moves into src/host/ with FSD's lower three layers
(shared, entities, features) and the client half takes the five-layer
layout, both checked by Steiger, matching dsh-lingualab. Entries and
assembly stay at the src/ root; integration tests move to test/.
Once implemented it supersedes the directory parts of D5 and D11.
The build never emptied lib/, so outputs of moved or deleted sources
stayed tracked (lib/features/session/, lib/skill-install.*), and the
CI parity diff cannot see leftovers. scripts/clean-lib.mjs now removes
lib/ first; the rebuild deletes 36 stale or test-only files.

tsconfig.build.json excludes only *.test.ts, so test helpers under src/
were compiled into lib/ and shipped. The 14 integration tests and their
4 helpers move to test/integration/, and the admin and password-login
harnesses move to test/ beside the existing ones. Vitest, ESLint,
run-gates, the slice-check whitelist, the development docs and the
bundled skills follow the new paths. Step 1 of the D29 plan.
Move src/client into shared, entities/account, four features
(change-password, admin-users, logout, nav-icon) and the
account-section widget, each slice behind an index.ts barrel.
index.tsx stays at the client root; there is no pages or app layer.

lint:fsd now runs Steiger over both halves, and slice:check only
accepts client files under the five layer folders plus index.tsx and
its test. lib/ is rebuilt from the new layout.
Move D29 to implemented/ with its text describing the landed layout
and register it in docs/decisions.md. D5 (layered src with barrels)
and D11 (HTTP endpoint layer) are superseded by D29 and move to
archived/ unchanged; their hashes seed archived/.manifest.json. D9
stays implemented because its injection rule still applies.

The old src-refactor-plan docs gain a note that D29 supersedes them.
tsconfig.client.build.json excluded only *.test.tsx, so the five
client *.test.ts files emitted .d.ts and .d.ts.map into lib/client/
and shipped with the package. Exclude *.test.ts as well.

bundle:check now fails if any *.test.* or *.spec.* file appears
anywhere under lib/, so the leak cannot come back unnoticed.
A top-level GET that arrives cross-site (Sec-Fetch-Site: cross-site,
Sec-Fetch-Dest: document) carries the gate's Lax cookie but not dsh's
SameSite=Strict session cookie, so dsh answered 401 and a reload kept the
cross-site context. For requests the gate allows, serve a static bounce page
that re-navigates via location.replace(location.href); the retry is
same-origin, so the Strict cookie is sent and the bounce cannot loop.
development's D29 (cross-site navigation bounce) shipped first, so the FSD layering record is renumbered to D30. cross-site-bounce moves into src/host/shared/guard/ and its integration test into test/integration/.
The route paths, the password-changed notice key and the URLs built from
them were written more than twenty times across both halves, with two
separate LOGIN_PATH definitions on the host side. src/protocol/index.ts
now defines them once (pure constants and functions, no imports). The
host enters through src/host/shared/protocol/ and the client through
src/client/shared/api/protocol.ts. slice:check allows only those two
bridges to import the contract.

The p11 source pin for the client redirect now asserts that the client
uses the contract constant instead of comparing literals. Records D31
and updates the structure docs.
scripts/check-host.mjs checks what the dsh host relies on and no type
checker sees: the package name, the cordis.patch.yml Loader row, the
client registration id and the host entry name agree; every manifest
target exists after a build; every require() in lib/client.js and every
CLIENT_EXTERNALS entry is a module the shell seeds (mirrors dsh
PLATFORM_MODULES, logged as debt in .rsp/TechDebt.md); no route literal
lives outside src/protocol, every registration names a protocol
constant, every protocol path is registered, and every route the READMEs
mention exists.

--self-test runs eighteen broken fixture trees, one per rule, and fails
if any of them passes. The gate runs in verify after bundle:check, in
the CI build job and in the build scenario of run-gates.
Extend the D31 cross-half contract from route paths to the request,
response and error bodies of the /auth endpoints. src/protocol/payloads.ts
holds them as pure types (StatusResponse, AdminUserRow, UsersResponse,
the password-change and admin-reset request/response/error bodies,
UserRole, SessionKind, PasswordRule, Unchecked<T>), re-exported by
src/protocol/index.ts and reached through the two existing bridges.

Host: the status, users, reset and password-change writers are typed
with the contract bodies; form fields are read through the typed
formField<Request>() helper; the user and session entities take
UserRole, SessionKind and PasswordRule from shared/protocol, so no @x
import is needed.

Client: the hand-written admin-types.ts mirror is deleted.
AccountStatusView derives from StatusResponse, AdminUserRow comes from
shared/api, form and result types move to features/admin-users/model,
request bodies are built from contract-typed objects via encodeForm,
and parsers read keys through Unchecked<T>. Runtime behaviour is
unchanged.

Docs: D31 records, the decisions index, the D30 placement table and
docs/specs/development(_zh).md describe the payload half.
@changeset-bot

changeset-bot Bot commented Oct 16, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: d639678

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@TecFancy
TecFancy merged commit dbc9b04 into main Oct 16, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant