* feat: attribute files and downloads to actors
* chore: refresh preview after staging migration
* fix: use a dash for missing actor identity
* fix: complete actor attribution and file creator UI
* fix: refine file creator identity UI
Keep historical identifiers readable and addressable while generating all new entity IDs with Base62. Reject unsafe external ID references and preserve API keys and device codes during optional normalization.
* feat(ids)!: normalize persistent identifiers to Base62
BREAKING CHANGE: historical ZPan-owned IDs and public tokens require the one-time normalization migration; old public links and stored credentials are invalidated without runtime fallback.
* fix(ids): namespace public redirect tokens
Generate share tokens as s plus 11 Base62 characters and image tokens as i plus 11 Base62 characters. Dispatch /r by namespace prefix, reuse the single resolved record, and update migration validation, contracts, documentation, and tests.
* fix(ids): guard future object storage keys
* fix(ids): normalize historical references safely
* fix!: unify pagination and realtime change delivery
Replace offset paging on affected unbounded collections with signed keyset tokens and infinite loading. Persist scoped resource changes so one global SSE connection can resume and invalidate query caches safely.
BREAKING CHANGE: migrated list APIs now accept pageToken and return nextPageToken instead of page and total fields.
Refs #450
* fix: keep page tokens at the HTTP boundary
Move signed page-token handling out of the pure domain layer so dependency-cruiser architecture checks pass without changing behavior.
* fix: route background job stats through usecase
Keep the HTTP boundary from reaching directly into repository ports and cover the new usecase wrapper.
* fix: align clients and checks with cursor pagination
* refactor: unify pagination boundaries and infinite loading
Replace opt-in profile listings with an opt-out private flag and a unified privacy endpoint.
BREAKING CHANGE: showOnProfile, listedAt, and the profile-listing endpoints are replaced by private and PUT /api/shares/:token/privacy.
Centralize request audit recording, preserve immutable download-task history, and derive hourly statistics and backfills from the same authoritative sources. Add durable user registration facts so admin deletion no longer destroys signup history.
Retain purged matter and image metadata while hiding tombstones from live reads. Record exact storage deltas and build hourly trends only after the opening balance.
* feat: make forcePathStyle configurable per storage
Previously hardcoded to true, which breaks S3-compatible backends that require
virtual-hosted-style addressing (e.g. Alibaba Cloud OSS). Now configurable via
admin storage settings with a toggle switch, defaulting to true for backwards
compatibility.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* test: cover storage force path style
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: saltbo <saltbo@foxmail.com>
* feat(avatars): host avatars + team logos on Cloud via SDK 2.4.0; remove public-bucket mode
Host user avatars and org logos on the ZPan Cloud avatar service
(zpan-cloud-sdk ^2.4.0) instead of a public S3/R2 bucket, then remove the
now-dead storages.mode / public-bucket concept entirely (#456 parts 2-3).
- image-upload gateway: upload/delete via SDK uploadAvatar/deleteAvatar against
a bound Cloud client; validate mime (AVATAR_CONTENT_TYPES) + size
(MAX_AVATAR_BYTES) before the call; map cloud error codes to 400/403/413/500;
unbound instance returns 503 cloud_required (delete is a best-effort no-op).
- licensing-cloud: createAvatarUploadClient builds the client with a plain-object
bearer header so both the image content-type and Authorization survive hono's
per-request header merge (a Headers instance would be dropped).
- drop storages.mode (migration via drizzle-kit), StorageRepo.select() no longer
takes a mode, remove StorageMode / Storage.mode / mode schema+audit+UI+i18n and
the PUBLIC_IMAGES bucket + PUBLIC_IMAGES_URL wiring.
Agent-Profile: https://agent-kanban.dev/agents/f759c704c282d88a
* ci(deploy): drop dead PUBLIC_IMAGES R2 provisioning from CF deploy
The Cloud avatar migration removed the PUBLIC_IMAGES binding from
wrangler.toml, so the deploy workflow's R2 public-images steps are dead and
must go too — otherwise every CF deploy keeps re-provisioning a public-read
zpan-public-images bucket (the footgun #456 eliminates) and sets an unused
PUBLIC_IMAGES_URL secret. Removes the bucket-create, managed-public-URL, and
secret steps (steps.r2 was only consumed by the secret step). Also drops a
stale storage-modes line from the v2.0 roadmap.
Agent-Profile: https://agent-kanban.dev/agents/f759c704c282d88a
---------
Co-authored-by: Alex Chen <alex-chen@mails.agent-kanban.dev>
Resolve#448 — one upload entry point and an AIP-164 soft delete.
Upload: POST /objects now returns size-decided upload instructions
{ sessionId, partSize, urls }; the server picks single PutObject (<=5 GiB)
vs 5 GiB-part multipart (>5 GiB) and rejects >5 TiB. The client PUTs each
slice, reads its ETag, then POSTs them to
POST /objects/{id}/uploads/{sid}/completions (returns the live object).
DELETE /objects/{id}/uploads/{sid} aborts and discards the draft.
Trash: matters.status drops 'trashed' (enum is {draft,active}); trash is
tracked by the existing trashedAt timestamp. DELETE /objects/{id} now
soft-deletes; the recycle bin lives under /trash/objects (list roots, get,
restorations, purge). Empty-trash is a frontend loop over roots.
BREAKING CHANGE:
- removes PUT /objects/{id}/status and POST /objects/{id}/uploads
- PUT .../uploads/{sid}/status -> POST .../uploads/{sid}/completions {parts}
- DELETE /objects/{id} flips hard-purge -> soft-delete; permanent purge
moves to DELETE /trash/objects/{id}
- DELETE /trash removed; restore is POST /trash/objects/{id}/restorations
- matters.status enum loses 'trashed' (migration backfills to trashedAt)
The migration swaps the matters_active_name_uniq partial index to exclude
trashed rows (WHERE status='active' AND trashed_at IS NULL). The single-PUT
presign is header-free so the uniform slice uploader's raw PUT matches the
S3 signature. Go downloader client + agent reworked to the unified flow.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(server): rename routes/ to http/ (clean-arch step 1)
The HTTP delivery layer was already split per-resource; align the directory
name with the hono-cf-clean-arch standard. Pure mechanical move via git mv;
updates the three server-side importers (app.ts, image-hosting-domain
middleware, openapi/downloader). No behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(server): add clean-arch backbone + migrate activity to a repo
Introduce the composition root and dependency-injection seam:
- usecases/ports.ts (barrel) + usecases/ports/<resource>.ts: framework-free
port interfaces and DTOs
- usecases/deps.ts: the Deps aggregate consumed via c.get('deps')
- composition.ts: createDeps(platform) — the only place adapters are built
- app.ts sets deps in request context after platform middleware
First adapter: adapters/repos/activity.ts (ActivityRepo) replaces
services/activity.ts. All 14 call sites rewired (routes use
c.get('deps').activity.*; auth.ts and transitional services construct the repo
from db). DTOs are now plain shapes, not drizzle $inferSelect.
Behavior-preserving: typecheck + 3807 tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(server): extract StorageRepo + migration tracker
services/storage.ts -> adapters/repos/storage.ts (StorageRepo). All 14 callers
rewired (http/middleware via c.get('deps').storages.*; transitional services via
createStorageRepo(db)). Port DTO reuses the shared Storage contract with Date
timestamps; the S3-credential 'Storage' type alias across 9 files now points at
StorageRecord. Data-layer test moved next to the repo.
Adds docs/clean-arch-migration.md as the living progress tracker.
typecheck + lint + 3807 tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(server): extract Profile/Announcement/Notification repos
- profile -> ProfileRepo; the pure buildBreadcrumb moves to domain/breadcrumb.ts
- announcement -> AnnouncementRepo; notification -> NotificationRepo
- All callers rewired (routes via c.get('deps').*; auth.ts + services via
create<X>Repo(db)); data-layer tests moved next to their repos
- Test infra: createApp accepts an optional deps; createTestApp returns deps so
tests fake a port by spying on testApp.deps.* (events SSE failure test no
longer spies the service module)
typecheck + lint + 3807 tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(server): extract OrgRepo (authz) + InviteRepo
- org -> OrgRepo (findPersonalOrg/getMemberRole/canReadOrg/canWriteToOrg/
isPersonalOrg); rewired across 4 routes + 2 auth middlewares + auth.ts
- invite -> InviteRepo; rewired invite-codes route + auth.ts
- data/unit tests for org & invite moved next to their repos
typecheck + lint + 3807 tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(server): extract BackgroundJobRepo (+ BackgroundJobError to ports)
background-jobs -> adapters/repos/background-job.ts. The BackgroundJobError
(caught by http for status mapping) moves to usecases/ports per the standard.
Rewired: background-jobs route + events SSE (deps) + archive-processing
(transitional repo). Unit + data tests relocated.
typecheck + lint + 3807 tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(server): extract QuotaRepo from effective-quota
The foundational quota leaf. effective-quota.ts -> adapters/repos/quota.ts
(QuotaRepo); the pure currentTrafficPeriod moves to domain/quota.ts; DTOs
(EffectiveQuota, CurrentStoragePlan) move to ports. Rewired 14 callers
(http -> deps.quota; services/auth/entry-node/workers.scheduled -> createQuotaRepo).
scheduled-worker test now mocks the adapter (createQuotaRepo) instead of the
service module.
typecheck + lint + 3807 tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(server): extract TeamRepo + TeamInviteRepo
team -> adapters/repos/team.ts (TeamRepo; composes QuotaRepo for quota totals);
team-invite -> adapters/repos/team-invite.ts. teams-admin + teams routes use
c.get('deps').{teams,teamInvites}. Data tests relocated.
typecheck + lint + 3807 tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* build(arch): enforce clean architecture via dependency-cruiser (ratchet) in CI
Adds .dependency-cruiser.cjs with the full hono-cf-clean-arch rule set and wires
pnpm lint:arch into CI. The drizzle-only-in-repos rule uses a shrinking
MIGRATION_PENDING allowlist so it passes today while still enforcing every
already-migrated layer; each future migration commit removes an entry. platform/
(Database driver type) and auth.ts are permanent named exceptions.
Currently green: 222 modules / 926 deps, 0 violations.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(server): combine user + org-entitlements into UserAdminRepo
Resolves the pre-existing user <-> org-entitlements import cycle by merging both
into adapters/repos/user-admin.ts (UserAdminRepo); shared types (UserWithOrg,
QuotaEntitlementItem, UserOperationFailure, entitlement inputs) move to ports.
users + teams-admin routes use c.get('deps').userAdmin.
typecheck + lint + lint:arch + 3807 tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(server): extract SiteInvitationRepo
site-invitations -> adapters/repos/site-invitations.ts. Route uses
c.get('deps').siteInvitations; the email helper now receives siteName from the
handler (http stays out of adapters); auth.ts uses the repo. Result-type unions
moved to ports.
typecheck + lint + lint:arch + 3807 tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(cf): fix storages.cf-test seed after StorageRepo extraction
cf-tests are excluded from typecheck; biome had pruned the transiently-unused
createStorageRepo import during the storage migration. Restore the import and
convert the platform.db seed calls. test:cf green (57 passed).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(spec): introduce BDD-lite spec/ + spec<->test traceability lint
Adds the standard's product-spec layer:
- spec/*.feature (Gherkin, no Cucumber runner) — one per capability, scenarios
tagged @<capability>/<slug> + layer; spec/README.md documents the convention
- [spec: <id>] breadcrumbs on home tests
- scripts/lint-spec.mjs + pnpm lint:spec (wired into CI): every scenario id must
have a referencing test and every breadcrumb must match a scenario
Specced: storages, announcements, notifications, invite-codes, site-invitations
(41 scenarios, all traced). Specs grow per capability as the migration proceeds.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(server): extract changelog + cf-custom-hostnames providers
Establishes adapters/providers/. changelog (GitHub releases/CHANGELOG) and
cf-custom-hostnames (CF for SaaS) move to adapters/providers/ behind
ChangelogProvider / CfHostnamesProvider ports (CfConflictError -> ports).
system + ihost-config routes use c.get('deps').{changelog,cfHostnames}.
typecheck + lint + lint:arch + 3807 tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(server): move db-transaction -> db/, path-template -> lib/
Two framework-free utilities leave services/ for their proper homes:
db/transaction.ts (the drizzle batch/transaction helper) and lib/path-template.ts
(object-key builder). Importers updated.
typecheck + lint + lint:arch + 3807 tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(server): migrate licensing subsystem drizzle to repos
license-state -> adapters/repos/license-binding.ts (LicenseBindingRepo);
instance-id + instance-info DB reads -> adapters/repos/instance.ts (InstanceRepo).
licensing/ (has-feature, refresh, entitlement, instance-info) now uses the repos
and imports no drizzle, so ^server/licensing leaves the dependency-cruiser ratchet.
licensing-admin route uses c.get('deps').{licenseBinding,instance}; service callers
construct the repos; instance-telemetry test mocks the adapter.
typecheck + lint + lint:arch + 3807 tests + 57 cf-tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(server): move S3Service to adapters/gateways behind S3Gateway port
Establishes adapters/gateways/ + deps.s3. S3Service -> adapters/gateways/s3.ts
(implements S3Gateway; S3StorageCredentials -> ports). A thin services/s3.ts
re-export shim keeps the http routes (objects/webdav/ihost/share-utils) and the
21 prototype-spy tests working unchanged until those routes migrate to deps.s3;
s3-dependent services can now move to usecases using deps.s3.
typecheck + lint + lint:arch + 3807 tests + 57 cf-tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(server): drain inline drizzle from me route (avatar -> ProfileRepo)
ProfileRepo gains setAvatar; the /api/me avatar handlers use c.get('deps').profiles
instead of inline user-table updates. 'me' leaves the dependency-cruiser ratchet.
typecheck + lint + lint:arch + 3807 tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(server): drain inline drizzle from quotas route (-> QuotaRepo.listOrgQuotaOverview)
The admin quota-overview join moves into QuotaRepo; the route uses
c.get('deps').quota. 'quotas' leaves the ratchet.
typecheck + lint + lint:arch + 3807 tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(server): SystemOptionsRepo drains auth-providers/system/email-config routes
New adapters/repos/system-options.ts (key-value access to systemOptions) + deps.systemOptions.
auth-providers, system, email-config routes drop inline drizzle and use
c.get('deps').systemOptions; all three leave the ratchet.
typecheck + lint + lint:arch + 3807 tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(server): drain inline drizzle from teams route (logo -> TeamRepo.setLogo)
TeamRepo gains setLogo; teams route uses c.get('deps').teams for logo set/clear
and drops its dead db locals. 'teams' leaves the ratchet.
typecheck + lint + lint:arch + 3807 tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(server): drain inline drizzle from ihost-config (-> ImageHostingConfigRepo)
New adapters/repos/image-hosting-config.ts + deps.imageHostingConfigs. The ihost-config
route's custom-domain CRUD uses c.get('deps').imageHostingConfigs (cf-hostnames already
via deps). 'ihost-config' leaves the ratchet.
typecheck + lint + lint:arch + 3807 tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(server): loadBindingState -> usecase, hasFeature/effectiveFeatures -> domain
Finishes the feature-gate path: domain/licensing.ts (pure hasFeature/effectiveFeatures),
usecases/licensing.ts (loadBindingState(deps) using LicenseBindingRepo + cert verify).
licensing/has-feature.ts deleted. Rewired 10 callers (routes/middleware via
c.get('deps'); services via createLicenseBindingRepo(db)). Tests retargeted to the
new modules (domain + usecases licensing).
typecheck + lint + lint:arch + 3807 tests + 57 cf-tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(server): extract StorageUsageRepo + storage-usage reservation usecase
The quota-reservation crown dependency. adapters/repos/storage-usage.ts
(StorageUsageRepo: rollbackReservations + reconcile); usecases/storage-usage.ts
(reserveStorageUsage/withStorageUsageReservation/StorageUsageMutationContext taking
{quota,storageUsage} deps); StorageQuotaExceededError -> ports. Rewired 9 callers
(objects/webdav/ihost routes via c.get('deps'); matter/image-hosting/archive/purge/
save-to-drive via constructed repos). Unblocks the matter/image-hosting clusters.
typecheck + lint + lint:arch + 3807 tests + 57 cf-tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(server): migrate 5 leaf service clusters to clean-arch (parallel wave)
Extracted 7 services via parallel agents on file-disjoint components:
- instance-telemetry -> usecases/instance-telemetry (reuses instance + systemOptions ports)
- image-upload -> adapters/gateways/image-upload (ImageUpload port, deps.imageUpload)
- archive-jobs -> adapters/gateways/archive-jobs (ArchiveJobsGateway, deps.archiveJobs)
- zip-compress + zip-extract -> adapters/gateways/zip + adapters/repos/zip (ZipGateway + ZipPlanRepo)
- object-upload-sessions -> adapters/repos/object-upload-session (ObjectUploadSessionRepo)
- purge -> usecases/purge (pure usecase over existing s3/storages/storageUsage)
Routes (objects/teams/me/internal/background-jobs) now reach these via c.get('deps');
entry files + workers build deps via createDeps(platform). Barrels wired by hand.
typecheck + lint:arch (240 modules) + 3810 tests + 57 cf-tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(spec): add quotas/profile/licensing feature specs + traceability
29 new scenarios traced to existing integration tests via [spec: id] breadcrumbs.
lint:spec: 70 scenarios, all covered.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(server): migrate auth/webdav/cloud/branding/image-hosting clusters (parallel wave 2)
17 services extracted via 5 parallel agents on file-disjoint components:
- auth-account: email->EmailGateway, share-notification->ShareNotificationRepo,
member-count->MemberCountRepo, captcha->domain+usecase, signup-mode/team-count->usecases
- webdav-middleware: api-keys/download-tokens gateways, webdav-state/webdav-path repos,
webdav-xml->domain (pure)
- cloud: licensing-cloud->LicensingCloudGateway, cloud-store/cloud-traffic-report/
remote-download-usage repos (cloud-traffic-metering + licensing-refresh-runner folded in)
- branding: pure usecase over existing deps (no new port)
- image-hosting: ImageHostingRepo
12 new deps fields wired by hand. WebDavMatterRow DTO moved into the webdav-path port
(was importing services/matter, which cycled through the ports barrel); domain WebDavMatter
dirtype widened to number|null to match the nullable column. Ratchet shrunk: ihost.ts +
middleware/image-hosting-domain.ts no longer touch drizzle. services/ now 26->9 (matter crown).
typecheck + lint:arch (261 modules, no cycles) + 3810 tests + 57 cf-tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(spec): add users/audit/teams/avatar/background-jobs/events/health specs
64 new scenarios traced to existing integration tests. lint:spec: 133 scenarios, all covered.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(server): migrate share/save-to-drive/archive-processing/trash-retention (parallel wave 3)
- share -> ShareRepo (+ domain/share, transitional ShareMatterRow DTO); shares.ts now
holds ZERO drizzle (dropped from the ratchet)
- save-to-drive -> pure usecase over deps (s3/storages/storageUsage/quota/activity/share)
- archive-processing -> usecase + ArchiveTargetFolderRepo (archive-jobs gateway self-assembles
its deps subset from platform to avoid a composition cycle)
- trash-retention -> pure usecase
purge gains deps.share for share cascade-delete. 2 new deps fields wired. services/ now 9->5
(matter, matter-name-conflict, downloads, s3 shim, site-public-origin remain).
typecheck + lint:arch (265 modules, no cycles) + 3810 tests + 57 cf-tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(spec): add branding/email-config/auth-providers/system/image-hosting/webdav/quota-store specs
128 new scenarios traced to existing integration tests. lint:spec: 261 scenarios, all covered.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(server): migrate the matter keystone + site-public-origin (wave 4)
The crown. matter (644 lines, 17 exports) -> adapters/repos/matter.ts (MatterRepo: full
drizzle CRUD + conflict resolution) + usecases/matter.ts (confirmUpload quota-guarded) +
usecases/ports/matter.ts (Matter DTO + NameConflictError); matter-name-conflict -> domain.
Fan-in of 10 rewired: objects/shares/trash routes now hold ZERO matter drizzle (via deps.matter);
webdav + archive-processing/purge/save-to-drive/trash-retention usecases + zip/webdav-path repos
repointed. site-public-origin -> domain (pure helpers) + usecase over deps.systemOptions.
services/ now 5->2 (only downloads + the s3 shim remain). 1 new deps field (matter).
typecheck + lint:arch (268 modules, no cycles) + 3810 tests + 57 cf-tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(spec): add redirect + download-tasks specs
44 new scenarios traced to existing integration tests. lint:spec: 305 scenarios, all covered.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(server): migrate downloads (remote-download) cluster (wave 5)
downloads/{core,mappers,types} (915 lines) -> adapters/repos/{downloader,download-task}
(DownloaderRepo + DownloadTaskRepo) + usecases/downloads.ts (assignment + task state
machine + remote-download credit billing) + usecases/ports/downloads.ts (DownloadError +
DTOs). Rewired download-tasks/downloaders/events routes + objects.ts upload handlers to
c.get('deps'). 2 new deps fields. services/ now down to ONLY the s3 shim.
typecheck + lint:arch (268 modules) + 3810 tests + 57 cf-tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(spec): add shares spec (32 scenarios)
lint:spec: 337 scenarios, all covered.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(server): delete the s3 shim — services/ is empty, clean-arch complete
Routed all 20 S3 call-sites in http (objects/webdav routes + share-utils consumers
shares/redirect/ihost/image-hosting-domain) onto c.get('deps').s3; webdav's no-c helpers
take an S3Gateway param. Repointed 17 test files off the shim onto adapters/gateways/s3.
Deleted server/services/s3.ts — server/services/ is now empty and gone.
Ratchet: dropped ^server/services (fully migrated); no-circular now fully enforced with
no path exemptions. MIGRATION_PENDING is down to 2 deliberately-deferred files
(http/webdav.ts listDescendants, middleware/auth.ts session lookup).
Also adds the objects spec (39 scenarios) -> 376 scenarios across 26 capabilities.
Final gates: typecheck + lint:arch (267 modules, no cycles) + lint:spec (376) + lint
+ 3810 tests + 57 cf-tests all green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(server): migrate the last 2 ratchet files — architecture fully locked
webdav.ts + middleware/auth.ts were the last files touching drizzle outside repos.
- WebDAV: listDescendants/PROPPATCH-touch/PUT-overwrite/COPY-rollback + Basic-Auth username
check moved to MatterRepo.{listActiveDescendants,trashByIds,restoreActiveByIds,touch,applyUpload}
+ UserAdminRepo.{isBanned,matchesUsername}. webdav.ts now imports no drizzle.
- Auth middleware: disabled-user (banned) check -> deps.userAdmin.isBanned.
Ratchet (MIGRATION_PENDING) is now empty and removed. no-circular + drizzle-only-in-repos
are fully enforced with zero exemptions; only platform/, test/, auth.ts remain as permanent
named exceptions. New methods covered by existing real-D1 webdav/auth integration tests.
typecheck + lint:arch (267 modules) + lint:spec (376) + lint + 3810 tests + 57 cf-tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(spec): spec the 4 remaining admin/auth capabilities
Closes the spec gaps for capabilities that had routes+tests but no .feature:
image-hosting-config (domain/CF custom-hostname admin), licensing-admin (cloud
pairing/binding/refresh), teams-admin (team admin + entitlements), auth-username
(username sign-up). 42 new scenarios traced to existing integration tests.
lint:spec: 418 scenarios across 30 capabilities, all covered.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(matter): listActiveDescendants uses exact-prefix (SUBSTR) not LIKE
Folder names can contain '_'/'%', which LIKE treats as wildcards and would
over-match descendants in WebDAV recursive COPY/MOVE. Reuse the repo's existing
descendantParentCondition (SUBSTR), consistent with getDescendants/cascadeParentPath.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(server): address review follow-ups (DTO dedupe, composition, dead locals)
- Dedupe transitional DTOs: ShareMatterRow + WebDavMatterRow -> the canonical Matter
port DTO (removes hand-copied duplicates + schema-drift risk; no cycle reintroduced).
- composition.ts: hoist shared stateless instances (one s3/storages/systemOptions
instead of constructing duplicates inline).
- Remove the 21 dead 'const db = c.get(platform).db' locals -> biome warning-free.
typecheck + lint:arch (267 modules) + lint:spec (418) + 3810 tests + 57 cf-tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(server): dissolve server/licensing into domain + usecases layers
server/licensing/ was a feature-grouped dir outside the layer taxonomy — its 3
orchestration files imported adapters directly, escaping usecases-no-infrastructure.
Now classified + enforced:
- public-keys -> domain/license-keys (pure)
- verify + cloud-event-token -> usecases/license-certificate (paseto/zod crypto helpers)
- entitlement/instance-info/refresh -> deps-first usecases (license-entitlement,
instance-info, license-refresh), using existing deps.{licenseBinding,instance,licensingCloud}
11 consumers rewired to deps; dead db param dropped from runLicensingRefresh. No barrel
changes. server/licensing/ deleted — every server file now sits in an enforced layer
(or a named exception: platform/test/auth.ts/lib/middleware).
typecheck + lint:arch (266 modules) + lint:spec (418) + 3810 tests + 57 cf-tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>