Skip to content

Latest commit

 

History

History
375 lines (286 loc) · 26.4 KB

File metadata and controls

375 lines (286 loc) · 26.4 KB

HANDOFF — What The Next Agent Needs To Know

Generated 2026-07-21 by the v2 module-system + sfmc-modules extraction session. If you're reading this in a new Claude session, start here.


0. TL;DR

We're midway through a two-axis refactor:

  1. Module protocol lift: v1 (routes / migrations / handlers in manifest, hand-written db-server routes per module) → v2 (modules declare permissions / services.provides / services.requires; platform provides db / config / service SDK drawers; business code lives in SAPI process)
  2. Module extraction: business modules move out of this repo into a separate repo. Goal: main repo becomes a pure SDK + API surface — no business logic

Per the user's directives:

  • Clean-break only. No compatibility shims. No "保留旧代码". When v2 lands, delete v1 fully.
  • Auto-commit as you go. Don't wait for user to approve each commit.
  • Modules are untrusted third-party. Never reach into SDK source on their behalf. Don't @sfmc-bds/sdk calls in the platform layer; don't assume module internals.

1. Architecture Now (v2)

┌────────────────────────────────────────────────────────────┐
│  Minecraft BDS                                             │
│  └─ behavior_packs/sfmc-modules/scripts/main.js (esbuild)  │
│     imports @sfmc-bds/sdk/runtime (90% of biz)                  │
│            + @sfmc-bds/sdk/sapi/db (db.tx / query / defineTable)│
│            + @sfmc-bds/sdk/sapi/config (config.get/set)         │
│            + @sfmc-bds/sdk/sapi/service (service.get)           │
│            + @sfmc-bds/sdk/module-loader (ModuleRegistry)       │
└────────────────┬───────────────────────────────────────────┘
                 │ HTTP @ 127.0.0.1:3001
                 ▼
┌────────────────────────────────────────────────────────────┐
│  db-server (Node 22.13+)                                   │
│   manifest-loader.ts ← v2 manifests                        │
│   schema-registry.ts ← collects db.defineTable calls       │
│   tx-runner.ts        ← /api/sfmc/db/tx POST               │
│   service-registry.ts ← service.get dispatcher             │
│   permission-gate.ts  ← startup + runtime 403              │
│   routes/db-routes / service-routes / config-routes        │
│   routes/lands.ts (dead code, kept for v1 modules to       │
│                    compile — see §5)                       │
└────────────────────────────────────────────────────────────┘

@sfmc-bds/sdk is a publishable npm package, not a private workspace package anymore. Module code references the published version (today: from modules/sdk/@sfmc-sdk via npm link during dev; will be registry @sfmc-bds/sdk@^0.1.0 after publish).


2. Status As Of 2026-07-21

Done

Area State
SDK db / config / service subpaths ✅ Implemented (modules/sdk/@sfmc-sdk/src/sapi/{db,config,service})
SDK transformed to publishable npm package private: false, exports for db/config/service added, npm pack passes
db-server protocol plumbing ✅ schema-registry / tx-runner / service-registry / permission-gate / module-auth + 3 new routes mounted
v2 manifest loader + strict validation db-server/src/manifest-loader.ts
land module fully migrated to v2 ✅ land-transfer / land-validate / land-audit, db.tx + db.audit + tx.call;GUI 已并入同一模块
land + land-gui deleted v1 src files ✅(land-gui 包随后并入 land,独立 feature-land-gui 已移除)
Documentation rewrite ✅ docs/dev/{README, manifest-contract, sdk-reference, module-author}.{zh,en}.md + docs/marketplace.{zh,en}.md all lifted to v2 four-drawer
Tanya7z/sfmc-modules repo created + skeleton pushed https://github.com/Tanya7z/sfmc-modules, 2 commits on main
v2 protocol runtime fixes ✅ PR #3 (5e6fe15) — fixed 7 BLOCKERs: route body parsing, /configs/all auth exemption, tx moduleId spoofing, db:write:* assertion, schema-registry finalize/softDelete SQL, land-gui catalog registration, enabled-flip regression. tools/smoke-modules.js now passes 100%
afk v2 migration ✅ Pure config.get module (zero db / service), commit 1681b0a
spawn-protect v2 migration ✅ Pure SAPI lifecycle module (zero db / service / config), commit 95013d4
chat-sounds v2 migration ✅ Pure chatSend subscriber, commit f5eee5c
5 area modules → feature-area v2 consolidation ✅ PR #4 (42f58db) — merged fly + creative + survival + peace + clean into single feature-area module with unified areas[].features.<name> config schema. Single ModuleRegistry.register + 5 sub-lifecycles
URL normalization finalization ✅ All Shiroha7z/sfmc-modules → Tanya7z/sfmc-modules, author fields restored to Shiroha7z signature handle
tools/smoke-modules.js spawnSync import ✅ Fixed
v2 manifest permission key regex ✅ Now accepts 2-segment service:<name> AND 3-segment db:op:* / config:op:*

Not Done (where to pick up)

15 v1 modules remaining:

  • Wave 1 (independent, no db): inventory-switcher, qa, tps, monitor
  • Wave 2 (simple db): online-time, activity-log, daily-task
  • Wave 3 (provides-economy keystone): money — unlocks restoring land's economy refs
  • Wave 4 (depends-on-money/economy): chat, chat-gui, coop, coop-gui
  • Wave 5 (cross-domain): scoreboard-sync
Area Notes
After each v2 migration → git subtree push to Tanya7z/sfmc-modules Then git rm -rf modules/packages/<id> here
Main repo modules/ clearance + catalog.json remote-fetch-only switch Only after ALL 19 → v2 migrated
Delete db-server/src/routes/lands.ts + db-server/src/domain/land.ts + land DDL from schema.ts Only after all 15 v1 modules migrated (right now it's dead code needed for v1 compilation)
Restore land's economy refs (requires:[feature-economy], permission:service:economy.account, tx.call('economy.debit')) When feature-economy lands in v2 (Wave 3)
Restore feature-spawn-protect etc. to enabled=true by default After smoke proves all 19 stable
Publish @sfmc-bds/sdk to npm Currently local-only. npm publish from modules/sdk/@sfmc-sdk once release-ready
npm run build --workspaces after SDK export changes Keep CLI / db-server / bds-tools in sync with new subpaths

3. User's Standing Rules (Verbatim From Recent Conversations)

These are non-negotiable. Encode them in behavior, not just notes:

"我的要求其实是每次重构时都不要保留旧代码" — Clean-break only. Don't leave compatibility shims, dual implementations, or fallback paths. Delete the old when the new lands.

"你一路进行下去自己commit就行 不用等我" — Auto-commit. Don't pause for approval on each commit. Group logically related changes; commit when a unit of work lands.

"你需要把package里面的模块全部想象成是其他作者写的东西 所以你当然不能去碰sdk把这些包写在sdk里" — Modules = untrusted third-party. Don't import module source from anywhere in the platform. Modules may depend on @sfmc-bds/sdk; platform may NOT touch module internals.

"我们的目标是让主仓库变成一个纯净的sdk与api提供者 其他功能都在模块仓库内" — Main repo modules/ should end up EMPTY. Everything in it moves to the external registry. Keep @sfmc-bds/sdk + platform infra only.

"主仓一次 subtree push 全部 24 个模块" — Was the earlier ask; we pivoted to per-module migration because v1 modules don't compile cleanly with v2 dispatch yet. Per-module is the de-facto plan now (one v2 module at a time → push → remove).


4. URL Normalization (CRITICAL)

Throughout this repo, references to the module registry use Shiroha7z/sfmc-modules. They MUST be updated to Tanya7z/sfmc-modules everywhere. Tanya7z IS Shiroha (same GitHub user, the gh CLI token is under Tanya7z). Verified with gh api /user — login = Tanya7z, name field = Shiroha.

Search and replace:

grep -r "Shiroha7z/sfmc-modules" --include="*.{ts,js,mjs,json,md,yaml,yml}"

Currently inconsistent files (from memory of recent edits):

  • tools/fetch-module.mjs — registry source default
  • tools/.sfmc-registry-cache.json (if exists)
  • docs/dev/module-author.{zh,en}.md
  • docs/marketplace.{zh,en}.md
  • README.md
  • modules/catalog.json (comment)
  • docs/dev/README.md (none, ok)

When grep finds 0 hits for Shiroha7z/sfmc-modules, we're done with normalization. Also check metadata.author URLs in any package.json if they reference the org.

Note: sfmc-modules/ local skeleton at D:/#WorkPlace/sfmc-modules/ — its README.md still says Shiroha7z/sfmc-modules. Should also normalize.


5. The land V2 Module — Read This As The Template

modules/packages/land/ is the complete working v2 example. When migrating the next v1 module, copy this shape.

modules/packages/land/
├── sapi/
│   ├── manifest.json       ← schemaVersion 2; permissions; services.{provides,requires}
│   ├── tsconfig.json
│   └── src/
│       ├── index.ts        ← ModuleRegistry.register({ id, lifecycle: { registerPermissions, init, cleanup } })
│       ├── land-validate.ts  ← pure SDK, returns ValidationResult
│       ├── land-transfer.ts  ← db.tx() with tx.insert/update/audit/call
│       └── land-audit.ts     ← db.query<T>("land_audit_logs", { where, orderBy, limit })
└── configs-default/

Key v2 patterns to copy:

// lifecycle.init:
await db.defineTable("lands", {
  id: { type: "text", primary: true },
  owner_player_id: { type: "text", notNull: true, index: true },
  ...
}, { softDelete: true });

// business:
export async function transferLand(input) {
  return db.tx(async (tx) => {
    await tx.update("lands", input.landId, { owner_player_id: input.newOwnerId });
    await tx.audit("lands", input.landId, "transfer", { from, to });
    await tx.call("economy.debit", { playerId, amount: 100 });
    return { ok: true };
  });
}

When a v2 module consumes services from another v2 module (e.g. gui calls economy.account.get, or historically land-gui called land.byId), the consumer's manifest.json MUST declare in services.requires and permissions: ["service:land.byId", ...]. Validation throws at startup otherwise.


6. Migration Workflow For The Remaining 22 Modules

For each v1 module M in modules/packages/M/:

  1. Inspect: Read v1 manifest + src/index.ts. Identify which db-server/src/routes/*.ts endpoints it uses. Identify all configs/<X>.json keys it touches. Identify cross-module deps (HttpDB calls to other modules).
  2. Write v2 manifest:
    • schemaVersion: 2
    • permissions: declare db:read:<table>, db:write:<table>, config:read:<key>, config:write:<key>, service:<name>
    • services.requires: each consumed service
    • services.provides: each exposed service (if any)
    • NO routes / migrations / seeds / handlers / events — validator throws
  3. Rewrite src/index.ts:
    • Top of file: ModuleRegistry.register({ id, afterWorldLoad: false, lifecycle: { registerPermissions, init, cleanup } })
    • Replace HttpDB.raw calls with db.query / db.get / db.insert / db.update / db.delete / db.tx
    • Replace direct ConfigManager calls with config.get / config.set
    • Replace cross-module calls with service.get (or tx.call inside transactions)
  4. Rewrite business into SDK calls. Delete HttpDB-land-specific logic. Replace Lands.Transfer(...) style with await transferLand(...) exports. Pure functional where possible.
  5. Update catalog.json: entry.path → modules/packages/M/sapi/src/index.ts (already correct for land).
  6. Smoke test: node tools/smoke-modules.js (requires db-server live on 3001). Should show module M loaded with N services, no startup throw.
  7. Clean-break delete: git rm -rf modules/packages/M/sapi/src/ (only the v1 src files; the v2 src lives elsewhere or is rewritten in-place). Wait — currently land's v2 src lives IN sapi/src/. So we keep the dir, just delete v1 files in it. After migration, sapi/src/ is all v2 code.
  8. Commit: feat(<id>): v2 migration — db.tx + service.get (auto, no approval).
  9. Subtree push to Tanya7z/sfmc-modules — BUT only after the whole batch is well-tested. Otherwise the public registry gets broken modules.
  10. Better: push each well-tested module as soon as it passes smoke — this keeps sfmc-modules repo synced incrementally.

7. Constraints And Edge Cases

  • DB tables: no cross-module collisions. Two v2 modules declaring the same table name = startup throw. If feature-money declares accounts and so does feature-economy, they must coordinate. In practice each module owns its tables; sharing requires explicit requires in the consumer's manifest and the provider exposing a service interface.

  • Service names: globally unique. Schema: <moduleId>.<verbOrNoun> e.g. land.byId, economy.debit. Validate at startup.

  • TxContext is per-SAPI-process. All await tx.insert/update/... calls compile into a single POST /api/sfmc/db/tx payload when db.tx(fn) resolves. Order = code order.

  • config cache semantics. config.get hits memory after first call. config.set updates memory + async persists. There is no transactional config read; if you need consistency, use db.tx + config.set outside the tx.

  • HMAC tokens for service.get are per-moduleId. db-server module-auth.ts derives them from process.env.SFMC_MODULE_SECRET + moduleId. Modules don't see their own tokens; the SDK client (@sfmc-bds/sdk/sapi/service/client.ts) handles this transparently.

  • Strict TypeScript everywhere. exactOptionalPropertyTypes: true. Watch out for where: undefined — pass where: null or omit.


8. File Map (Updated Through 2026-07-21)

Platform (main repo)

db-server/src/
├── manifest-loader.ts                ← v2 only, throws on schemaVersion != 2
├── schema-registry.ts                ← collects db.defineTable
├── tx-runner.ts                      ← /api/sfmc/db/tx
├── service-registry.ts               ← /api/sfmc/services/:name
├── permission-gate.ts                ← startup + runtime 403
├── module-auth.ts                    ← HMAC token derivation
├── routes/
│   ├── db-routes.ts                  ← NEW
│   ├── service-routes.ts             ← NEW
│   ├── config-routes.ts              ← NEW
│   ├── modules.ts                    ← shows v2 info
│   └── lands.ts                      ← DEAD CODE (kept until v1 mod wave done)
├── domain/
│   ├── land.js / land.ts             ← DEAD CODE (ditto)
│   └── schema.ts                     ← land DDL inline; will be split out
└── index.ts                          ← boot order: 1) SQLite 2) manifest 3) registry 4) routes 5) listen

modules/sdk/@sfmc-sdk/
├── package.json                      ← publishable (private removed)
├── src/
│   ├── sapi/
│   │   ├── runtime/index.ts          ← Msg, Command, Permission, debug, Money, MenuNavigator
│   │   ├── db/index.ts               ← db.defineTable / tx / query / get / insert / update / delete / audit / idempotent
│   │   ├── db/types.ts               ← WhereExpr, ColumnDef, TxContext, TxStep
│   │   ├── db/client.ts              ← HTTP transport
│   │   ├── config/index.ts           ← config.get / set / onChange
│   │   ├── service/index.ts          ← service.get / list
│   │   └── host/                     ← platform layer adapter
│   ├── module-loader/index.ts        ← ModuleRegistry.register, installHostBootstrap
│   ├── contracts/                    ← shared types (LandData, CoopData, Channel, etc.)
│   └── node/                         ← db-server / qq-bridge / bds-tools / sfmc internals
└── README.md

modules/packages/land/                ← TEMPLATE — done(含 GUI;原 land-gui 已并入)
modules/packages/<other>/             ← v1 NOT YET migrated

tools/fetch-module.mjs                ← default source = github:Tanya7z/sfmc-modules@latest (REQUIRES URL FIX)
tools/check-ootb.js                   ← pre-boot self-check
tools/check-catalog.js                ← catalog.json integrity
tools/smoke-modules.js                ← e2e v2 loader test
tools/lock.js                         ← fingerprint / drift
tools/install-module.js               ← install / uninstall

External

D:/#WorkPlace/sfmc-modules/           ← LOCAL SKELETON, pushed to https://github.com/Tanya7z/sfmc-modules
├── packages/                         ← modules live here eventually
├── index.json                        ← registry
├── tools/{check-modules.js, sync-index.js, new.sh}
├── README.md
├── CONTRIBUTING.md
├── LICENSE
└── .github/workflows/ci.yml

9. Recommended Order For The Next Agent

  1. First: fix URL normalization (5 min grep-and-replace). One commit: chore(platform): normalize sfmc-modules registry URL Shiroha7z → Tanya7z.
  2. Second: smoke-test land with the current state — node tools/smoke-modules.js — confirm the v2 path actually works end-to-end before touching the next module. (This is important: the v2 wire-up was originally done in a session that ran out of context; haven't re-run smoke since.)
  3. Third: pick the next v1 module. Easiest target: one with no cross-module deps, single db-server/src/routes/<mod>.ts. Looking at the deleted git log, candidates: AFK / SPAWNPROTECT (independent, no required services). Read its v1 code, follow §6 workflow.
  4. Cleanup pass at the end of each migration:
    • git rm deleted-v1 files within the module
    • Commit
    • Run smoke
  5. Don't publish @sfmc-bds/sdk to npm yet — wait until at least 5 modules are v2, smoke covers the matrix of (db.tx, tx.call, service.get inside tx, audit, idempotent).
  6. Don't delete db-server/src/routes/lands.ts and friends until all v1 modules migrated.

10. If You Get Stuck

  • db-server throws at startup with moduleId=X schemaVersion=Y (需要 2): that module's manifest is still v1. Update it per §6 step 2.
  • service.get 403: missing service:<name> in permissions AND missing entry in services.requires. Add both.
  • db.defineTable succeeds but later db.query 403 table-not-permitted: missing db:read:<table> in permissions.
  • where: undefined causes TypeError: SDK strict-types — pass null or omit entirely.
  • tx.call("svc.name", ...) returns "service not registered": target service's module is enabled but hasn't reached the service.provide(...) init phase yet. Wait for afterWorldLoad = false modules to finish init. Restart is safe.
  • esbuild during BP build complains Could not resolve "@sfmc-bds/sdk/...": main repo's modules/sdk/@sfmc-sdk/package.json exports field missing the subpath. Add to "exports" map.
  • stale module-commands after SDK change: rebuild @sfmc-bds/cli (npm run build --workspace @sfmc-bds/cli) and check sfmc/dist/module-commands.js.

11. Final Words

The work is not done but the foundation is solid. v2 protocol works, SDK is publishable, land proves the pattern, sfmc-modules repo exists. Remaining work is mechanical: migrate 22 v1 modules following land as the template, push each to Tanya7z/sfmc-modules, delete from main repo, repeat.

When all 22 done + smoke passes, that's the green light to:

  • git rm -rf modules/packages here
  • delete db-server/routes/lands.ts + domain/land.ts + land DDL
  • rewrite catalog.json to remote-fetch only
  • final commit: feat(platform): main repo now pure SDK + API surface — modules migrated to sfmc-modules

After that, the user's directive is fully executed.


12. Session Progress (2026-07-22)

End-of-day check-in. Counts and gotchas that will trip up the next agent.

12.1 What got done in this session (commits on main since 2026-07-21)

Module / change Commit Notes
chore(platform): finalize URL normalization Shiroha7z → Tanya7z 1e49999 All repo URLs normalized; package.json#author left as Shiroha7z (signature, not login). See memory handoff_orientation.md for the GitHub identity mapping
feat(afk): v2 migration 1681b0a Pure config.get, no db
fix(platform): v2 manifest validation + land cross-module dep + smoke import 1e866de (1) permission-gate regex now accepts 2-segment service:<name> (was 3-segment only); (2) land manifest had requires:[feature-economy] and permission:service:economy.account withdrawn — restored when feature-economy lands; (3) tools/smoke-modules.js was missing require("node:child_process")
feat(spawn-protect): v2 migration 95013d4 Pure SAPI lifecycle
feat(chat-sounds): v2 migration f5eee5c chatSend subscriber
fix(ci): 修复 ootb workflow 持续失败 — ESM 崩溃、入口路径、缺失 configs 及 node:sqlite 版本门槛 (PR #12 → merged as a823baf) a823baf CI workflow fix. Closed obsolete PR #8–#11 first. Node pinned 22.5 → 22.13 because db-server imports node:sqlite at module scope and that builtin needs --experimental-sqlite on 22.5.0–22.12.x
feat(tps): v2 migration 8d11fa9 Provides tps.current + tps.status services
feat(qa): v2 migration 3aead3a Question bank restored from b6906a4's pre-clean configs-default/questions.json into modules/packages/qa/configs-default/qa.json
feat(online-time): v2 migration f45ea68 Module-owned player_onlinetime table; doesn't pollute platform sfmc_players.onlinetime_* columns anymore
feat(data-backup): v2 migration 6584069 core module, stays in main repo. db.tx against platform sfmc_players / sfmc_world
feat(monitor): v2 migration 474e197 First v2 module that consumes another module's service (tps.current). Dropped direct @sfmc-bds/module-tps npm dependency
feat(activity-log): v2 migration 7e81129 19-event audit logger → sfmc_activities. v1 used camelCase columns, v2 schema is snake_case — name mapping done in flush()
feat(economy): v2 migration d9fcbda Keystone. Provides 7 services: economy.{account.get,credit,debit,transfer,dailyTasks.list,dailyTasks.submit,stats.monthly}. Total provides jumped 17 → 24
feat(daily-task): v2 migration 45b67ce First v2 module-to-module service.requires chain end-to-end. requires:[feature-economy], services.requires:[economy.dailyTasks.list, .submit]
feat(scoreboard-sync): v2 migration b8d420c World scoreboard backup/restore via sfmc_scoreboards
feat(coop): merge coop + coop-gui into v2 287c6bd Merged the v1 modules that had a circular @sfmc-bds/module-coop@sfmc-bds/module-coop-gui import. New modules/packages/feature-coop/ with coop-api.ts (db.tx wrappers for all 18 v1 endpoints) + coop-core.ts (business logic) + index.ts (commands /coop, /coop create, /coop join, /coop leave, /coop bank, /coop rank, /coopshop)
feat(inventory-switcher): v2 migration + restore v1 configs from backup a5ff417 (1) v2 module; (2) also moved 7 config files out of configs-package/ backup into per-module configs-default/ (areas, banned_items, clean, peace_filters → feature-area; land → land; grids → inventory-switcher; settings → afk). configs-package/ directory removed

Total v2 migrations landed in this session: 16 modules. The remaining v1 modules are feature-chat + feature-chat-gui (decided to defer because the same conversation weight cost that activity-log and feature-coop already consumed was deemed too high for chat+chat-gui in a single context).

12.2 Critical state of the v2 protocol

Manifest validation is enforced, but service dispatch is metadata-only. Every services.provides declaration passes db-server startup validation, but service.get(name, input) and tx.call(name, input) actually throw "service 未注册" (or similar) at runtime — there's no cross-process handler bridge yet. This is why feature-coop / feature-economy / daily-task each declare real provides/requires chains that validate cleanly but the calls don't return real data. The PoC bridge is in db-server/src/service-registry.ts but only used for in-process dispatches.

The next agent who picks up chat-or-bridge wiring needs to either:

  • implement the cross-process service dispatch (the host-adapter work that gets mentioned in every commit message's "P1 note"), or
  • defer feature-coop / feature-economy / daily-task's UI / cross-module work until then, since their service.get calls currently 403 at runtime.

12.3 v1 routes that are now safe to delete

db-server/src/routes/{lands,economy,coops,scoreboards,activities}.ts are no longer used by any v2 module — feature-land, feature-coop, feature-economy, feature-scoreboard-sync, feature-activity-log all go through db.tx against the platform bootstrap tables. routes/players.ts and routes/world.ts are still used by feature-data-backup's db.tx writes (also against platform bootstrap tables, but the route files still get imported by _shared.ts and server.ts routing). The five routes in the first list can be git rm'd immediately. The user's reaction to "should we delete dead code" was "yes, clean-break", so consider this pre-approved once chat is migrated (chat still needs routes/messages.ts + routes/channels.ts + routes/chat-redirect.ts).

12.4 Storage state

  • 17 v2 modules loaded (16 module commits + core-data-backup)
  • 27 services registered (provides total)
  • All v2 modules enable cleanly with HMAC token derivation
  • tools/smoke-modules.js passes after PR #3 fixes; the final step (enable-flip) fails for unrelated reasons — see step §10 troubleshooting

— End of HANDOFF.