Skip to content

Commit 98301d4

Browse files
chore(deps): engine ranges 2.x → ^3.8.0 — and the de-dup blocker is a release, not a range (#33)
* chore(deps): the export lane compiles against the engine it targets C0 checklist item 1, open since the engine cut 3.0.0 and unticked through eight minor releases. All six @ai-rpg-engine/* ranges go 2.x → ^3.8.0: content-schema ^2.0.1, core ^2.0.1, modules ^2.1.0, pack-registry ^2.0.2, character-creation ^2.0.2, equipment ^2.0.2. Six ranges is what the tree holds, which is what the contract doc said — this time verified against the package.json rather than the prose. Zero source changes and zero fixture churn: build clean, 132 files / 2403 tests green. That is the finding, not an absence of one. C1 had already repaired everything the major boundary actually moved forge-side (the engineVersion range, the twelve real module ids, the content hash), so the remaining 3.x surface the converters touch — EquipmentSlot, ItemRarity, GameManifest, PackMetadata, DialogueNode.text — either did not change or was WIDENED across the major. version-skew.json predicted three drifts; none of them break a type. What they cost instead is documented in ENGINE_CONTRACT.md rather than in a compiler error. The lockfile is regenerated SURGICALLY, not wholesale. A full `rm package-lock.json && npm install` also bumped 94 unrelated transitive packages (pixi.js 8.16→8.19, react 19.2.4→19.2.8, playwright 1.59→1.62, parse-svg-path 0.1.2→0.2.0 — a 0.x minor, which is a major); every one of those is a way for this errand to break something it never touched. So the seven @ai-rpg-engine entries were stripped from the lock and re-resolved on their own: 7 of 229 packages changed, and `npm ci` reproduces all six at 3.8.0. ⚠ The first `npm install` did NOT produce a coherent tree, and the diff looked fine. npm resolved the direct deps to 3.8.0 but left the hoisted copies at 2.x for the transitive `*` ranges it already had on disk, so content-schema@3.8.0 sat on core@2.0.1 — two copies of one package, which is a type-identity break waiting to happen. `npm ls` is what said so; the lockfile diff did not. Verified after: one core in the tree, at 3.8.0. Refs C0 item 1 · [[2p5d-forge-engine-dep-bump-kickoff]] §1.1 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(deps): ⚠ ANDON — the hash de-duplication is blocked by a release, not a range The errand was briefed to delete `content-hash.ts` and import the engine's canonical `computeContentHash`, on the standing reasoning that only the 2.x dep ranges stood in the way. The ranges are bumped. The import is still impossible, and the reasoning was wrong before this errand started. MEASURED: `computeContentHash` does not exist in any PUBLISHED @ai-rpg-engine package. It lives in `content-schema/src/gate.ts` — a file C1 ADDED to engine `main` (merge `00001de`, 2026-07-29) and which has never been released. npm's `latest` for @ai-rpg-engine/content-schema is 3.8.0, published 2026-03-07, months before C1 existed. Installed all 26 published 3.8.0 packages and grepped: zero hits for `computeContentHash`, `runLoadGate` or `applyContentPack`. The blocker is a release; publishing is not this errand's to do (§2/§3), so I stopped. Consequences, both recorded rather than worked around: - `content-hash.ts` STAYS. Its header carried the false justification ("2.x has no such export") and now carries the measured one, because a comment that has drifted into being wrong is the exact failure C0 documented — DEFAULT_MODULES became nine phantoms that way. - the engine-side cross-repo equivalence test is NOT retired. Retiring it was justified by "with one shared function it asserts a tautology"; there is still no shared function, so it is still the only thing between two implementations and a silent divergence. `engine-deps-3x.test.ts` makes both halves mechanical instead of remembered. The first block pins the bump: six ranges declared at ^3.8.0 (enumerated from package.json, so a seventh cannot ride along undeclared), six RESOLVING at 3.8.x, and the stamped `>=3.8.0 <4.0.0` agreeing with the range that is installed. The second block is the ANDON: it asserts the C1 gate surface is absent and FAILS the moment a release makes the de-dup possible, with the four steps written in the failure's own comment. The resolution assertion is not redundant with the declaration one. The first install of this errand declared all six at ^3.8.0 and still left content-schema@3.8.0 sitting on core@2.0.1 — npm reused the 2.x copies it already had for the transitive `*` ranges. The lockfile diff looked fine. ⚠ Every assertion in the ANDON block is an ABSENCE, and a broken import looks identical to one. So it opens with a positive control proving the probe can see an export at all. Worth the four lines: the control failed on first run — I had guessed the export name — which is the only reason I know it discriminates. Refs [[2p5d-forge-engine-dep-bump-kickoff]] §1.2 · C1 REPORT §7.6 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(deps): the forge resolves its own module ids against a booted engine C1 put the decisive module check in the engine repo for one reason, written into both files: world-forge could not boot a 3.8.0 engine to resolve ids against, because its dependencies were 2.x. That reason is gone. One devDep — `@ai-rpg-engine/starter-fantasy@^3.8.0`, ONE package and 19 lines of lockfile, no transitive additions since its four deps are already this package's deps — and the four checks land in the file whose header already described the gap: boot a published starter, read its ModuleManager, resolve every id `convertManifest` emits. Twelve for twelve. That is inside the errand's stated budget (devDeps + one test file), so no ANDON. The engine-side check is NOT retired or demoted, and the header now says why in a way "it lives there because we can't do it here" did not. It resolves against the engine's own unreleased `main`; this resolves against what `npm install` actually hands a consumer. Those are two different engines right now — see the sibling ANDON commit, where that gap is a whole unpublished module — so an id that resolves in one and not the other is a fact worth failing over, not duplicated work. Both controls are here because a resolution test with neither proves only that the ids are plausible: - RED: the nine retired phantoms resolve against nothing, 9 for 9. These are the exact strings that used to ship, so the negative is historical input rather than an invented one. - the boot itself is asserted before anything is resolved against it. A silently empty registry would fail the resolution test in a way that reads exactly like phantom ids returning, and send the next reader into convert-pack.ts after a bug that is not there. The near-miss pairs are checked against the LIVE registry — traversal-core resolves where movement-core does not — instead of against RETIRED_PHANTOM_MODULES, which the structural block above already asserts against itself. Also corrected while rewriting the header: it pointed at `packages/cli/src/c1-forge-manifest.test.ts`, which does not exist. The checks are in `c1-gate.test.ts`. Noted in the file rather than quietly fixed — a cross-repo pointer nobody follows is how the EB-011 comment rotted into nine phantoms. 133 files / 2412 tests. Refs [[2p5d-forge-engine-dep-bump-kickoff]] §1.3 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(deps): the contract describes the engine that exists ENGINE_CONTRACT.md opened with "This exporter depends on the ai-rpg-engine 2.x API" and an eight-item checklist to work "*before* bumping the dep ranges." The engine reached 3.8.0 with not one box ticked, and the ordering is why: bumping first is what makes drift visible, and holding the ranges back is what let nine nonexistent module ids ride in every manifest this exporter wrote. The 4.x checklist at the bottom now says bump FIRST. Every claim re-measured against the installed 3.8.0 `.d.ts` files and a booted engine, not carried over. Items 1/3 closed by this errand, 2/3 by C1, 5/6/7 verified closed, 4 ANDON'd, 8 recorded: ⚠ ITEM 4 IS AN ANDON, not an oversight. TONE_MAP 8/8 and DIFFICULTY_MAP 3/3 match at 3.8.0. GENRE_MAP does not — 3.x added `mercantile` and `pursuit`, the genres of the two newest starters, and convert-pack.ts:147 silently falls them back to 'fantasy'. Two identity entries LOOK mechanical. They are not, because the forge has three genre vocabularies that already disagree: `project.genre` is a free string, the editor picker offers six fixed options that match neither list (SaveTemplateModal.tsx:12), and GENRE_MAP has eleven keys onto nine targets. Mapping two strings the editor cannot author reaches them only by hand-editing a project file. Which layer owns genre is C3's question. Two overclaims caught while writing, both from trusting a shape instead of reading it: - item 5 first read "structurally guarded, an engine change breaks the build." Guarded in ONE direction. `Set<ItemDefinition['slot']>` with literal members fails to compile if the engine REMOVES a slot and silently omits one it ADDS — the same shape as the GENRE_MAP gap two items up. Now says so, and names the fix (derive from EQUIPMENT_SLOTS). - item 6 reads "closed as far as it can be", not "closed". At 3.8.0 `EntityBlueprint.type` is still a bare string with no enum, so there is nothing for the six-roles-onto-two-types collapse to be verified against. The item reads clean because it is unconstrained. Item 7 closed with zero fixture churn, and the note it used to carry ("dialogue text is an array of { text } blocks on 2.x") never described what convert-dialogues emits — a plain string, then and now. 3.8.0's `string | TextBlock[]` widened the constraint out of existence rather than satisfying it. Item 8 is NOT done: no bump, no tag, no publish. It is a standing release note in the doc — retargeting the exporter 2.x→3.x is breaking for anyone resolving this package's engine peers, so the release that ships it takes a major and says so. Also recorded, deliberately not acted on: `@ai-rpg-engine/character-creation` is declared and imported by nothing (dropping a dep from a published package is consumer-visible), and convert-pack.ts:5 imports VALID_GENRES/TONES/ DIFFICULTIES type-only and never uses them — which is the ready-made handle for the drift guard item 4 will want. The known-blocker section carries the content-hash ANDON so the next reader finds it in the contract rather than only in a test comment. Refs [[2p5d-forge-engine-dep-bump-kickoff]] §1.4 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(deps): flip the forge-side 2.x claims, and pin contentHash at the type level Three places in this repo asserted a world that the dep bump ended. Two were wrong in a second way that only surfaced when I went to correct them. `export.ts` — `ExportedManifest` intersects `contentHash` into `GameManifest`, and said it did so because "contentHash was added to the engine's GameManifest in engine 3.8.0+, but this repo's core dependency is still installed at 2.0.1." Both halves were wrong. `core` resolves 3.8.0 now, AND the published 3.8.0 `GameManifest` has no `contentHash` — the field was added by C1 on engine `main` (`bf496e7`) and never released, the same unpublished surface that blocks the hash de-duplication. So the intersection stays for a different reason than the one it recorded: not a range behind, a release behind. That correction is now a TYPE-LEVEL PIN rather than a comment, which is the whole lesson of the nine phantom module ids. `PinnedContentHashState` resolves to one literal while the installed `GameManifest` lacks the field and to another the moment it gains it, so a published engine breaks `npm run build` with the fix written in the type's own name. ⚠ I PROVED IT FAILS. Injected `contentHash?: string` into the installed `core/dist/types.d.ts`, rebuilt, and got the intended error: engine-deps-3x.test.ts(165,11): error TS2322: Type '"absent-from-published-manifest"' is not assignable to type '"PUBLISHED — drop the ExportedManifest intersection in export.ts"' then restored node_modules with `npm ci` and confirmed zero residue. A pin that has never fired is a comment with extra syntax. `c0-export-table.test.ts` — the six 2.x version literals here are NOT flipped, deliberately. They are a frozen C0 stamp sitting beside `forgeCommit: 'feat/c0-alignment-audit'`; bumping the versions while leaving the commit would make the artifact claim the C0 audit ran against 3.8.0 deps, which it did not. A dated record stays dated. Commented as a frozen stamp, with a pointer to where current dependency truth actually lives (`engine-deps-3x.test.ts`, which reads it live). Also corrected here: a second pointer to `packages/cli/src/c1-forge-manifest.test.ts`, which does not exist — same dead reference as the one in `c1-manifest-truth.test.ts`. The checks are in `c1-gate.test.ts`. Two files pointed at a file nobody had opened, which is the EB-011 failure mode in miniature. 133 files / 2413 tests. Refs [[2p5d-forge-engine-dep-bump-kickoff]] §1.5 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(deps): handback — two ANDONs, one of them the errand's own premise Short, because this was an errand. Records what landed, the two halts, and the four things the advisor should look at before merging: the engine-side scope call (I flipped four version-skew items where the brief named one, and items 2/3 revert cleanly if that reads as over-reach), the GENRE_MAP ANDON routed to C3, the one-directional item-slot guard, and two declarations left deliberately untouched. Also carries the errors, since they are the reusable part: the type pin proven to fail by injecting the field and rebuilding; the positive control that failed on first run and is the only reason I know the ANDON block discriminates; and two files pointing at a test file that does not exist. Compensator table included — nothing beyond the authorised set was done. Refs [[2p5d-forge-engine-dep-bump-kickoff]] §5 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(deps): correct the 3.8.0 publish date — the night before C1, not months The errand's evidence conflated npm's package 'created' timestamp (2026-03-07, the v1.x-era first publish) with 3.8.0's publish time (2026-07-28T23:12Z). The conclusion stands unchanged — the C1 gate surface has never been published — and the corrected fact sharpens it: the packages were cut the night before C1 merged. Advisor amendment at the verification gate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent 86e54f1 commit 98301d4

9 files changed

Lines changed: 698 additions & 107 deletions

File tree

docs/engine-deps-3x/HANDBACK.md

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# HANDBACK — world-forge engine deps 2.x → 3.x (2026-07-29)
2+
3+
Short, because this was an errand. Two PRs open and unmerged, advisor gate.
4+
5+
| | |
6+
|---|---|
7+
| world-forge | [PR #33](https://github.com/mcp-tool-shop-org/world-forge/pull/33), branch `chore/engine-deps-3x`, 5 commits off `86e54f1` |
8+
| ai-rpg-engine | [PR #15](https://github.com/mcp-tool-shop-org/ai-rpg-engine/pull/15), branch `chore/engine-deps-3x-pin-flip`, 1 commit off `00001de` |
9+
| Suites | forge **133 files / 2413 tests** (was 132 / 2403) · engine **338 / 6684**, unchanged |
10+
| Publishes / tags / bumps | **none** |
11+
12+
Merge order: world-forge first. The engine PR only *describes* what the forge
13+
PR does.
14+
15+
## The four scope items
16+
17+
**§1.1 — six ranges bumped. DONE, and it was uneventful in a way worth
18+
recording.** All six declare `^3.8.0` and all six resolve 3.8.0. Zero source
19+
changes, zero fixture churn, suite green on the first run. C1 had already
20+
repaired everything the major boundary actually moved forge-side; the rest of
21+
the 3.x surface either did not change or was widened.
22+
23+
Two things not to repeat:
24+
25+
- The lockfile was regenerated **surgically**. A full
26+
`rm package-lock.json && npm install` also bumped 94 unrelated packages
27+
(pixi.js 8.16→8.19, react 19.2.4→19.2.8, playwright 1.59→1.62,
28+
`parse-svg-path` 0.1.2→0.2.0 — a 0.x minor, i.e. a major). Stripping the seven
29+
`@ai-rpg-engine` entries and re-resolving only those changed **7 of 229**.
30+
- ⚠ The first `npm install` produced an **incoherent tree that looked fine in
31+
the diff**. npm resolved the direct deps to 3.8.0 and kept the 2.x copies it
32+
already had for the transitive `*` ranges, so `content-schema@3.8.0` sat on
33+
`core@2.0.1`. `npm ls` said so; the lockfile diff did not. There is now an
34+
assertion for resolved versions separate from declared ranges.
35+
36+
**§1.2 — the hash de-duplication. ⚠ ANDON, not done.**
37+
38+
The premise was wrong, and it was wrong before this errand started. `content-hash.ts`
39+
said it existed because "2.x has no such export, so importing it is a dependency
40+
bump, not an import." The bump happened; the import is still impossible.
41+
`computeContentHash` lives in `content-schema/src/gate.ts`, which **C1 added to
42+
engine `main` and which has never been published**. npm `latest` is 3.8.0,
43+
published 2026-07-28 — the night before C1. Installed all 26 published 3.8.0
44+
packages and grepped: zero hits for `computeContentHash`, `runLoadGate`,
45+
`applyContentPack`. **The blocker is a release, not a range.**
46+
47+
Consequences, all recorded rather than worked around:
48+
49+
- `content-hash.ts` stays; its header now carries the measured reason.
50+
- The engine-side cross-repo equivalence test is **not retired**. Retiring it was
51+
justified by "one shared function makes it a tautology" — there is no shared
52+
function.
53+
- `GameManifest.contentHash` is the same gap (added by engine `bf496e7`,
54+
unpublished), so `export.ts` still intersects the field in locally.
55+
- All three are **pinned to fail when a release lifts the block**, not left to
56+
memory: a runtime pin in `engine-deps-3x.test.ts` and a type-level pin that
57+
breaks `npm run build`.
58+
59+
**§1.3 — forge-side live module resolution. DONE, and cheap.** One devDep
60+
(`@ai-rpg-engine/starter-fantasy`, one package, 19 lockfile lines, no transitive
61+
additions) and four checks in `c1-manifest-truth.test.ts`: boot a published
62+
starter, read its `ModuleManager`, resolve all twelve emitted ids. Inside the
63+
stated budget, so no ANDON.
64+
65+
The engine-side check is **not** demoted. It resolves against the engine's
66+
unreleased `main`; this resolves against what `npm install` hands a consumer.
67+
Those are two different engines right now — see §1.2 — so an id resolving in one
68+
and not the other is a fact worth failing over.
69+
70+
**§1.4 — `ENGINE_CONTRACT.md` rewritten.** Every claim re-measured against the
71+
installed 3.8.0 `.d.ts` files and a booted engine. Checklist now **5 closed, 3
72+
open**, matching `version-skew.json` exactly. Item 8 is a **standing release
73+
note**, not a bump.
74+
75+
**§1.5 — pins flipped**, engine and forge. Detail in the two PR bodies.
76+
77+
## What the advisor should look at
78+
79+
1. **Engine-side scope.** The brief said "keep it to the flip" and named item 1.
80+
I flipped **four** items (1, 7 mine; 2, 3 closed by C1 and never flipped),
81+
because leaving them would have regenerated a `version-skew.json` I knew was
82+
wrong while `c1-gate.test.ts` two directories over already asserted the
83+
opposite. If that reads as over-reach, items 2 and 3 revert cleanly on their
84+
own. Everything else engine-side is comment corrections in the same file plus
85+
a dated addendum to C0's REPORT. **No production code touched.**
86+
87+
2. **`GENRE_MAP` (checklist item 4) — ANDON, routed to C3.** 3.x added
88+
`mercantile` and `pursuit`. Two identity entries *look* mechanical. They are
89+
not: the forge has **three genre vocabularies that already disagree** — a free
90+
`string` in the schema, six fixed options in the editor picker
91+
(`SaveTemplateModal.tsx:12`, matching neither list), eleven `GENRE_MAP` keys
92+
onto nine targets. Mapping two strings the editor cannot author is half a fix.
93+
Which layer owns genre is a C3 question.
94+
95+
3. **A one-directional guard, now labelled.** `convert-items.ts` types its slot
96+
and rarity sets off `ItemDefinition` and then writes the members as literals.
97+
The engine **removing** one breaks the build; the engine **adding** one is
98+
silently omitted — the same shape as the `GENRE_MAP` gap. Deriving both from
99+
`EQUIPMENT_SLOTS` / `ITEM_RARITIES` closes it. Not done: out of scope, and it
100+
deserves its own change.
101+
102+
4. **Recorded, deliberately not acted on.** `@ai-rpg-engine/character-creation`
103+
is a declared dependency nothing imports (dropping it is consumer-visible);
104+
`convert-pack.ts:5` imports `VALID_GENRES` / `VALID_TONES` /
105+
`VALID_DIFFICULTIES` type-only and never uses them — which is the ready-made
106+
handle for the drift guard item 4 will want.
107+
108+
## Errors worth carrying
109+
110+
- **I proved the type pin fails.** Injected `contentHash?: string` into the
111+
installed `core/dist/types.d.ts`, rebuilt, got the intended `TS2322` naming
112+
the fix, then restored with `npm ci` and confirmed zero residue. A pin that has
113+
never fired is a comment with extra syntax.
114+
- **The positive control caught me.** Every assertion in the ANDON block is an
115+
absence, and a broken import looks identical to one — so it opens by proving
116+
the probe can see an export. That control **failed on first run**: I had
117+
guessed the export name. It is the only reason I know the block discriminates.
118+
- **Two files pointed at `packages/cli/src/c1-forge-manifest.test.ts`, which
119+
does not exist.** The checks are in `c1-gate.test.ts`. Corrected with a note
120+
rather than silently — a cross-repo pointer nobody follows is exactly how the
121+
EB-011 comment rotted into nine phantom module ids.
122+
123+
## Compensators — nothing beyond the authorised table
124+
125+
| Action | Undo |
126+
|---|---|
127+
| `chore/engine-deps-3x` (world-forge) | `git push origin --delete chore/engine-deps-3x` |
128+
| `chore/engine-deps-3x-pin-flip` (ai-rpg-engine) | `git push origin --delete chore/engine-deps-3x-pin-flip` |
129+
| PR #33 / PR #15 | `gh pr close` — both unmerged |
130+
| Any commit | `git revert` per slice; each is independently revertible |
131+
132+
No publish, no tag, no version bump, no deploy, no deletion.

package-lock.json

Lines changed: 49 additions & 30 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/export-ai-rpg/package.json

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,17 @@
5050
"rpg",
5151
"typescript"
5252
],
53-
"//": "ai-rpg-engine/* deps: see src/ENGINE_CONTRACT.md for the 2.x API shape this exporter depends on and the checklist for a future 3.x bump.",
53+
"//": "ai-rpg-engine/* deps: see src/ENGINE_CONTRACT.md for the 3.x API surface this exporter depends on and what a future 4.x bump has to re-verify.",
5454
"dependencies": {
55-
"@world-forge/schema": "*",
56-
"@ai-rpg-engine/content-schema": "^2.0.1",
57-
"@ai-rpg-engine/core": "^2.0.1",
58-
"@ai-rpg-engine/modules": "^2.1.0",
59-
"@ai-rpg-engine/pack-registry": "^2.0.2",
60-
"@ai-rpg-engine/character-creation": "^2.0.2",
61-
"@ai-rpg-engine/equipment": "^2.0.2"
55+
"@ai-rpg-engine/character-creation": "^3.8.0",
56+
"@ai-rpg-engine/content-schema": "^3.8.0",
57+
"@ai-rpg-engine/core": "^3.8.0",
58+
"@ai-rpg-engine/equipment": "^3.8.0",
59+
"@ai-rpg-engine/modules": "^3.8.0",
60+
"@ai-rpg-engine/pack-registry": "^3.8.0",
61+
"@world-forge/schema": "*"
62+
},
63+
"devDependencies": {
64+
"@ai-rpg-engine/starter-fantasy": "^3.8.0"
6265
}
6366
}

0 commit comments

Comments
 (0)