Skip to content

AI guidance: co-located conformance pattern + one-command data-ai install - #164

Open
krisnye wants to merge 26 commits into
mainfrom
krisnye/ai-improvements
Open

AI guidance: co-located conformance pattern + one-command data-ai install#164
krisnye wants to merge 26 commits into
mainfrom
krisnye/ai-improvements

Conversation

@krisnye

@krisnye krisnye commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Description

Two related bodies of work on the @adobe/data-ai AI guidance and the samples that exercise it.

1. Co-located, type-first conformance pattern. Reworks the feature-architecture rules and skills so a data/state transform file exports exactly its function and its cases (typed Conformance<typeof fn> / Derivation<typeof fn>), eliminating per-transform .cases.ts + .test.ts clutter in favor of a single per-layer aggregator test that auto-discovers via import.meta.glob.

  • Injected services as named args (keyed by service-name-minus--service), with deterministic doubles that publish their exact responses, and strongly-typed effects (positive + negative compile-time checks).
  • Matcher-aware matches() honoring vitest asymmetric matchers (anyNumber/anyString) so the pure spec and the ECS satisfy the same case despite ECS-minted ids.
  • Four conformance runners (spec / transaction / action / computed), each with a barrel-driven coverage guard; computed conformance builds from the ComputedDatabase layer.
  • Folders organize by kind of type (data/ values, services/ service types incl. impls, ui/ presentation); ui/ isolation is strict and inviolable.
  • Assembled-db alias aligned to MainService; actions read current state from the store, never a cached computed.
  • All samples converted to the pattern (todo reference impl, tictactoe, solid-dashboard, react-pixie, p2p, space-rock).

2. One-command, managed data-ai install. install now both copies the bundle and wires the consumer repo to self-update — idempotently pinning the exact dev-dependency, adding the repo's own postinstall, and gitignoring the managed folders. Skips the wiring gracefully with --global or when there's no package.json, and prints a lockfile-sync reminder only when it actually (re)pins the dep. README rewritten to the single-command flow.

Related PRs

None.

krisnye and others added 19 commits August 6, 2026 15:23
…migrations

data-lit-todo is the reference implementation of the co-located conformance
pattern:
- transitions/derivations co-locate cases (Conformance<F> / Derivation<F>);
  one spec.test.ts aggregator dispatches on case shape
- transaction + action + computed conformance runners, all sourcing the same
  cases; coverage guards so none are missed
- side effects on transitions asserted via recording service doubles (no Proxy)
- expect.any(Number) matchers replace id-ignoring; shared per-entity toData
- Node 24 as the DRY repo standard (.nvmrc single source, CI reads it)

Also includes the earlier feature-architecture migrations of the other samples.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e pattern

- data/state.md: one file = function + cases (Conformance<F>/Derivation<F>,
  two-exports rule); anyNumber matchers; injected services + declared effects;
  single spec.test.ts
- conformance.md: matcher-aware compare; toData projection; the four runners
  (spec / transaction / action / computed) each with a coverage guard;
  recording doubles (no Proxy)
- transactions/actions/computed.md: central conformance aggregators; every
  transition has an action (state + effects); computed conforms to its derivation

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Iteration 1 refinement (from a sample conversion):
- conformance.md: computed runner builds the db from the ComputedDatabase layer,
  not the assembled MainService — a subscribing service/action above it would
  withCache the pre-seed value that a direct fromState seed can't invalidate
- state.md: add matchers.ts only when a minted value needs it; derivation `input`
  is a full State
- align the todo reference computeds.test.ts to build from ComputedDatabase

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… runners

Iteration 2 refinements (from a sample conversion):
- transaction coverage guard keys off the transactions barrel (registered
  mutations), not a file glob — a flat read helper no longer trips it
- splitAndRecordServices / the action runner tolerate a no-arg transition
  (args: undefined)
- conformance.md notes both as general rules

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s.test.ts

Iteration 3 refinement (from a sample conversion):
- state.md: a state/ derivation composes >=2 State fields; a value from a single
  field is that type's math on its data/<type> namespace (winner/status), not
  state/. A feature may have zero state/ derivations. (Removes a self-contradiction
  that had winner/status listed both as state/ derivations and as sub-type math.)
- conformance.md: a computed projecting one data/<type>'s math is conformed by
  that type's tests; a feature with no state/ derivation omits computeds.test.ts
  (an empty aggregator fails vitest)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ifications

Agent conversion (iteration 4) of data-lit-tictactoe to the pattern:
co-located cases, spec.test.ts, matcher-aware compare (no matchers needed — no
minted ids), barrel-driven transaction guard, no-arg-safe action conformance,
a state/ derivation (currentPlayer) + computeds.test.ts built from
ComputedDatabase; single-field board math stays on data/board-state.

Rule clarifications this surfaced:
- state.md: a state/ derivation takes the full State param (not a Pick)
- computed.md: a production computed wires the pure data/<type> helper, never
  imports a state/ derivation (whose module builds test-double cases)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tore

Naming: rename the assembled-db alias FeatureDatabase -> MainService and
feature-database.ts -> main-service.ts (folder-eponymous per the rule) across
todo, tictactoe, space-rock, gpu-hopper and all importers. The rule was already
correct (MainService); the samples now match it.

Staleness fix: an action reads current state synchronously from the store, never
from a cached computed (which refreshes only on committed transactions, so a
direct fromState seed can't invalidate it). Fixed tictactoe's playOpponentMove
and captured the rule in actions.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rollout conversion: co-located cases (Conformance<F>), single spec.test.ts,
matcher-aware compare, transaction + action conformance (barrel-driven guard),
new action layer. Scalar-resource-only feature, so per the rules it has no
to-data / computeds.test / matchers / data-<type> folders. All gates green
under Node 24 (42 tests).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rollout conversion: co-located cases, spec.test.ts, matcher-aware compare
(matchers.ts added — Sprite has ECS-minted ids), to-data + per-entity projection,
transaction + action conformance, new action layer. No derivations/services, so
computeds.test omitted per the rules. All gates green under Node 24 (54 tests).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…pattern

Rollout conversion of negotiation + presence: co-located cases, spec.test.ts,
matcher-aware compare, transaction + action conformance. Actions kept as plain
functions (not registered) — registering 9 actions hits the quadratic
Database.Plugin extends tsc budget; runtime-neutral (p2p UI uses transactions).
Action coverage guard is transition-driven (tolerates orchestration actions).
All gates green under Node 24 (74 tests).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rollout conversion (systems + injected random service, multi-archetype): co-located
cases, spec.test.ts, matcher-aware compare (ordered tuples + multiset entity bags),
transaction + action + system(tick-loop) conformance, per-entity to-data probing
three archetypes, random-service double. Real-time step transitions are realized
and conformed by the systems tick loop, not actions. All gates green Node 24 (156).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- conformance.md: array comparison is per-collection — ordered by default
  (display-ordered collections + Vec2 tuples), multiset for orderless entity bags
- actions.md: "every transition has an action" scoped to app-facing,
  transaction-backed transitions; per-frame/system transitions are conformed by
  the tick loop, not actions
- state.md: all non-state inputs go in the single args object (Conformance reads
  Parameters[1]); co-located cases must not touch the public.js barrel at module
  load (import cycle); keep genuine non-transition helper tests (create/predicate)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…sGameOver)

Per the refined rule: only redundant per-transform tests are removed; a genuine
helper (constructor / single-field predicate) keeps its own unit test. Restores
create.test.ts and is-game-over.test.ts deleted during conversion. (160 tests.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Align the packages' public assembled-db exports to the cross-feature naming rule:
TodoDatabase -> TodoMainService, TictactoeDatabase -> TictactoeMainService,
SpaceRockGameDatabase -> SpaceRockGameMainService.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Clarify why the computed-conformance covers state/ derivations and not trivial
single-helper computeds: a trivial observe.field -> data/<type>.helper computed is
already covered by its parts (field via toState, math via the helper's unit test),
so conforming it would be tautological. Escape hatches: promote to a thin state/
derivation to pin non-obvious wiring; promote an entity-folded value to a State
field so toState conforms the projection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds `data-ai init`: pins @adobe/data-ai in the consumer's devDependencies
(exact version), adds `data-ai install` to the consumer's OWN postinstall
(chaining if present — a dependency's postinstall does not run under pnpm), and
gitignores the managed bundle folders. Bumping the pinned version then refreshes
the rules on install with no manual step and no committed diff.

README rewritten: a "Recommended — managed, auto-updating" recipe (init +
install) with the pnpm caveat spelled out for the installing agent, the Claude
Code skills-plugin one-time step, and a one-shot no-dependency alternative.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…wires auto-updates

A single `install` now both copies the bundle and (idempotently) pins the exact
dev-dependency, adds the consumer's own postinstall, and gitignores the managed
folders. Skips the wiring gracefully with --global or when there's no package.json.
Removes the init-vs-install choice; updates HELP and README to the one-command flow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e dev-dep

When install adds/repins the exact @adobe/data-ai dev-dependency the lockfile is
out of sync until a normal install records it, so it now prints a "run pnpm
install" next-step — only when the pin actually changed, silent on idempotent re-runs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
{ name: "appends the first todo",
before: { todos: [], displayCompleted: false },
args: { name: "a", analytics: AnalyticsService.createFake() },
after: { todos: [{ id: anyNumber, name: "a", complete: false }], displayCompleted: false },

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

anyNumber doesn't seem to demonstrate the vite any number pattern we use.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 602c42f. The example now imports anyNumber with an inline // vitest expect.any(Number), and the matchers bullet shows the actual source so the pattern is explicit:

// matchers.ts
import { expect } from "vitest";
export const anyNumber = expect.any(Number);
export const anyString = expect.any(String);

Also noted matches() honors any vitest asymmetric matcher (expect.stringContaining, …), not just these two.

the spec the computed is *conformed to*, not a production dependency. Both the
computed and the `state/` derivation call the same `data/<type>` helper, so they
agree.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't we import state derivations in cases where they have good/equivalent performance? I believe the tic tac toe sample had good samples of delegating observables concisely to the pure computed functions.

I definitely agree that we should always prioritize maximum performance in the ECS, both here and in state transitions, but in cases where it doesn't matter (small N, resource computeds etc, reusing seems like it is fine)

Please review and verify my thinking on this.

Summary: ECS is about performance anywhere that performance can matter, but it is fine to reuse tested data functions where appropriate.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified — your thinking is correct, and I relaxed the rule in 602c42f.

I checked my own stated rationale and it was wrong for derivations. The rule justified the ban with "cases construct service test-doubles at load" — but that's true only of transform cases. A state/ derivation is (state) => value, takes no services, so its co-located cases are inert { input, value } data with no doubles, and they tree-shake out of the app build exactly like matchers.ts does. So module hygiene was never the real reason.

The real constraint is the one you named: performance. A derivation takes the whole State, so reusing it forces the computed to observe the full-state projection and re-run on any field change. That's fine for small-N / resource / scalar computeds (the tictactoe case — though note those actually delegate to data/board-state helpers, which read only what they need), and wasteful on a hot per-entity / large-N path, where you hand-wire minimal resource/index reads instead.

New guidance: prefer the data/<type> helper (both the computed and the state/ derivation call it, so they agree by construction); may import a pure state/ derivation where perf is adequate; hand-wire minimal reads where it matters. Reuse freely where it doesn't matter, optimize where it does. The one remaining hazard is a cases literal touching the public.js barrel at load — already forbidden in state.md.

…sing pure state derivations in computeds

- state.md: concise rationale for co-locating cases (spec-owned fixtures 4 runners
  reuse, drift-proof type, no double-exec, central coverage guard); show
  matchers.ts = expect.any(Number) so the vitest pattern is explicit.
- computed.md: relax the blanket ban on importing a state/ derivation into a
  computed — derivations take no services, so their cases are inert data that
  tree-shakes out; performance (a derivation observes full State) is the real
  constraint, not module hygiene.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
// todo's `expectMatches`, used where a compared value is not a whole `State`.
export const expectMatches = (actual: unknown, expected: unknown): void => {
expect(matches(actual, expected), `mismatch:\n actual ${JSON.stringify(actual)}`).toBe(true);
};

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, we are writing a custom expect matches for each feature? Is there no common or more reusable pattern that we could use? Let's think about this and examine several examples to see if anything common shakes out of this.

It seems that there should be standard patterns for converting state to ecs and comparing.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved — this is gone. The matcher-aware compare is now Match.matches/Match.assert in @adobe/data/testing (framework-agnostic, honors asymmetric matchers, float tolerance, and per-collection ordered/multiset via the unordered option). No feature writes expect-state-matches.ts anymore; space-rock just passes match: { unordered: new Set(["bullets","asteroids"]) } to the runners.

fromState/toState/toData(store,entity) are conformance-only and must never run in
production. Disambiguate the conformance toData(store,entity) reader from the
library's db.toData() serialization API (name collision only). Tie the remaining
anyNumber mention to expect.any(Number).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
{
name: "fires from a ship facing +x at rest",
before: { ...field, ship: { position: [100, 100], velocity: [0, 0], rotation: 0 }, bullets: [] },
args: undefined,

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

args: undefined

still needs to be provided? Seems like would be nice to be omittable only when there are no args.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. The shared Conformance.Case<State, Args> makes args optional exactly when the transform takes none — … & ([Args] extends [void] ? { args?: undefined } : { args: Args }) — so a no-arg case omits args entirely, and a real-arg case still requires it. fire-bullet cases now drop args.

| readonly Call<Args[K]>[]
| ReadonlySet<Call<Args[K]>>;
};

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a LOT of type machinery to replicate in every conformance case. We should find a common location to define these, perhaps in adobe-data types. Approve the type names/location with me before implementing.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This applies to almost all the types in this file.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — extracted into a shared @adobe/data/testing module (approved names/location up-thread), and migrated all six samples to it. Two namespaces:

  • Conformance — the case types (Case/Cases/DerivationCase/DerivationCases/Effects/ServiceCall), effect recording, generic id resolution, and the four runner drivers (runSpec/runTransactions/runActions/runComputeds).
  • Match — framework-agnostic matches/assert, anyNumber/anyString, and a ref(label) id-correspondence matcher (options: unordered, tolerance).

This file — all ~112 lines of it — collapses to a ~10-line alias that binds State once:

import type { Conformance as ConformanceApi } from "@adobe/data/testing";
import type { State } from "./state.js";
export type Conformance<F extends (...a: never[]) => unknown> = ConformanceApi.Cases<State, F>;
export type Derivation<F extends (...a: never[]) => unknown> = ConformanceApi.DerivationCases<F>;

Transform/derivation files are unchanged (cases: Conformance<typeof fn>). expect-state-matches.ts, record-effects.ts, matchers.ts, and expect-conforms.ts are deleted from every feature — ~180 lines of per-feature infra gone, replaced by the alias + the genuinely feature-specific projection (from-state/to-state/to-data). vitest is an optional peer dep. Shipped in commits 7b8261aaaeba48; @adobe/data 2935 tests green, all samples green on Node 24.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved — moved into shared @adobe/data/testing (Conformance + Match namespaces); this per-feature file is now a ~10-line alias / deleted. See the summary on the type-machinery thread above.

typeof value === "object" &&
value !== null &&
typeof (value as { asymmetricMatch?: unknown }).asymmetricMatch === "function";

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also seems that it would be worth sharing from somewhere else.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved — moved into shared @adobe/data/testing (Conformance + Match namespaces); this per-feature file is now a ~10-line alias / deleted. See the summary on the type-machinery thread above.

// (`PlayerMark`) rather than a whole `State`.
export const expectMatches = (actual: unknown, expected: unknown): void => {
expect(matches(actual, expected), `mismatch:\n actual ${JSON.stringify(actual)}`).toBe(true);
};

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ya, this is all way too much code to have to author for each feature.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved — moved into shared @adobe/data/testing (Conformance + Match namespaces); this per-feature file is now a ~10-line alias / deleted. See the summary on the type-machinery thread above.

krisnye and others added 2 commits August 6, 2026 22:55
… todo

New `@adobe/data/testing` subpath export, two namespaces:
- Match: framework-agnostic tolerant compare (matches/assert) honoring asymmetric
  matchers (anyNumber/anyString) and a `ref` id-correspondence matcher, float
  quantization, and per-collection ordered/multiset arrays.
- Conformance: the case types (Case/Cases/DerivationCase/DerivationCases/Effects/
  ServiceCall) bound to a feature's State via a ~10-line alias; effect recording;
  generic id resolution (fromState returns Id→Entity, `resolver` builds resolve);
  and the runSpec/runTransactions/runActions/runComputeds driver harnesses.

vitest is an optional peerDependency (runners register describe/it); Match stays
framework-free. `args` is now omittable when a transform takes none.

Migrate data-lit-todo to it: delete per-feature conformance-case types,
expect-state-matches, matchers, record-effects, expect-conforms (~240 lines);
runner test files keep only their bespoke apply/run adapters. 117 tests green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…to @adobe/data/testing

Each feature drops its hand-authored conformance-case types, expect-state-matches,
matchers, record-effects, and expect-conforms in favor of the shared toolkit; the
runner test files keep only their bespoke apply/run adapters.

Toolkit refinements from the harder samples:
- fromState may return void (index/singleton-addressed features need no id map).
- runTransactions `covers` lists transactions asserted outside the cases mechanism
  (space-rock setInput/setBounds — no data/ transform, direct resource assertion).
- arg splitters guard arrays.
- space-rock passes match:{unordered:{bullets,asteroids}} for its entity bags; its
  systems tick-loop test uses Match.assert.

Also tidy tictactoe: the `state` computed now composes the `board` computed with
the resource observables (Observe.fromProperties) instead of re-folding the
placed-mark entities — the mark-fold lives only in `board`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…he toolkit

- data-p2p-tictactoe: both features migrated. presence reproduces its per-peer
  userId seeding through runActions via a test-only concurrency that reads the
  peer id at apply time; actions `registered` is the transition-backed set (the
  action barrel also holds a streaming trackPresence with no transition analogue).
- data-ai rules: state.md / conformance.md / transactions.md / actions.md /
  computed.md / index.md now teach the shared @adobe/data/testing API (thin
  conformance-case alias, Match matchers incl. ref, the four Conformance.run*
  drivers, generic id resolution, covers/unordered/tolerance) instead of the
  deleted per-feature machinery.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* exhausted. Consumers' tests rely on this sequence to compute their expected
* `after` — it is part of the double's contract, not a hidden detail.
*/
export const fakeMoves = [4, 0, 8, 2, 6] as const;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This export does not adhere to our single export rules. It would be preferable if conformance tests just injected their own values.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. create-fake.ts is now a single export (createFake); the published schedule is folded inline as its default, and it is dropped from public.ts. Per your preference, conformance cases now inject their own valuesOpponentService.createFake([4]) — and assert against what they passed, so nothing reads a shared published constant. Applied to all five service doubles; features/services/index.md updated to this convention.

void badArgs;
void missingArgs;
void extraArg;
void dataKey;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are these type checks necessary per feature? this isn't covered by our new data testing types?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right — moved. The Effects positive/negative type-test now lives once in @adobe/data/testing (conformance/effects.type-test.ts), and every per-feature conformance-case.type-test.ts is deleted. The feature conformance-case.ts is now purely the ~10-line State-binding alias (plus the entity re-export).

run: (db) => toggleDisplayCompleted(db),
});
},
});

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this monolith exist? I thought we had an automated way to locate test cases and execute them?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved — actions.test.ts is now one Conformance.runActions({ makeDb, store, fromState, toState, transitions: <data/state glob>, actions: <barrel or actions-dir glob> }) call. It discovers actions and pairs each to its same-named transition automatically (like runSpec); no imports-and-conforms(...) list. See the full write-up on the thread below.

run: (db) => enterGame(db),
});
},
});

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See, this is another monolith. We need a better way rather than writing a monolithic test importer.

Let's think of some way to improve and simplify how we aggregate and run the tests that is not monolithic.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — the runners now auto-pair by name, so there is no importer to maintain. I traced every override to a spec↔ecs vocabulary gap and closed each at the source rather than in the test:

  • services → conform the action (reads db.services); the case's service args become recording overrides via makeDb.
  • identity → a Conformance.entity(specId) arg marker the runner resolves per side (spec→data-id, ecs→seeded entity); ecs ops take the entity under the transition's arg key.
  • arg shape → precise (non-Partial) invocation, so same-shape ops need no reshape.
  • granularity/infra → an op with no same-named transition is skipped (infra like setInput, or system-dispatched like collision); coverage is inherent to pairing, so the guards are gone.
  • name divergence → a thin same-named op (todo reorderTodo action, space-rock createInitial tx) instead of an adapter.
  • output shape → computeds compare identity by default; entity-id lists opt into hydrate.

A whole conformance test file is now just discovery. tictactoe's, in full:

Conformance.runTransactions({
  createStore, fromState, toState,
  transitions: import.meta.glob([".../data/state/*.ts", "!*.test.ts", "!*.type-test.ts"], { eager: true }),
  transactions,   // the facet barrel (or a dir glob)
});

The one irreducible residual is ambient user-scoped context — p2p's per-peer userId — which keeps a single seedContext hook. All six samples migrated (zero per-item adapters), define removed, Node 24 green. Shipped in d09029dff76c63.

krisnye and others added 2 commits August 7, 2026 01:23
…or todo + tictactoe

The ECS conformance runners now discover by glob and pair transition↔ecs-op by
name — no per-item `define`/`conforms` wiring. Overrides are eliminated by closing
the spec↔ecs vocabulary gaps at the source:
- services: the action reads db.services (conform the action, not the raw tx).
- identity: a `Conformance.entity(specId)` arg marker the runner resolves per side
  (spec→data-id, ecs→seeded entity); ecs ops take the entity under the transition's
  arg key.
- arg shape: precise (non-Partial) invocation, so same-shape ops need no reshape.
- granularity/infra: an ecs op with no same-named transition is skipped (infra or
  system-realized); coverage is inherent to auto-pairing, so the guards are gone.
- computed output: identity compare by default; entity-id lists opt into `hydrate`.

tictactoe: 3 tiny auto-pairing files, zero markers (index/service args). todo: same,
with `entity()` markers on addressed ids; entity-addressed ops take `{ id }`, and a
new `reorderTodo` action gives name-parity (dragTodo stays the UI-drag transaction).
`define` is kept temporarily so the not-yet-migrated samples stay green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… remove define; rules

Completes the auto-pairing migration across the fleet and removes the interim
`define` code path. Every ECS conformance runner discovers by glob and pairs
transition↔op by name; no per-item wiring, no coverage guards.

Lib (@adobe/data/testing):
- runTransactions/runActions/runComputeds are auto-only (name-pairing via
  discoverOps, which accepts a facet barrel OR a directory glob so ops kept out of
  the plugin facet still pair). `define`/`registered`/`covers` removed.
- `Conformance.entity(specId)` arg marker resolves per side (spec→data-id,
  ecs→seeded entity); `seedContext` residual hook for user-scoped ambient context.
- `Effects` type-test moved here once (per-feature type-tests deleted).

Samples (all zero-override, Node 24 green): solid-dashboard 32, react-pixie 42
(entity markers + {id} arg keys), p2p 53 (presence seedContext for userId;
per-transition actions discovered via actions-dir glob), space-rock 135
(createInitial transaction for name-parity; hitAsteroid/loseLife conformance
dropped — collision covered by collision-detection.test + spec). todo 102,
tictactoe 58 unchanged.

Service fakes: create-fake.ts is now a single export; published schedules folded
inline and cases inject their own values.

Rules: state/conformance/transactions/actions/computed/index + services/index
rewritten for auto-pairing (entity marker, seedContext, hydrate, name-parity,
inject-own-values doubles).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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