koota-kit 0.8.7 → 0.8.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.8 (2026-10-14)
4
+
5
+ <!-- Release notes generated using configuration in .github/release.yml at main -->
6
+
7
+ ## What's Changed
8
+ * test: prove registry caller composition in one World by @jbdevprimary in https://github.com/jbcom/koota-kit/pull/76
9
+
10
+
11
+ **Full Changelog**: https://github.com/jbcom/koota-kit/compare/v0.8.7...v0.8.8
12
+
3
13
  ## 0.8.7 (2026-10-14)
4
14
 
5
15
  <!-- Release notes generated using configuration in .github/release.yml at main -->
package/README.md CHANGED
@@ -138,6 +138,10 @@ ID. Listener errors occur after a committed publication; retrieve a token with
138
138
  `getRegistration(id)` if registration notification threw before `register`
139
139
  could return it.
140
140
 
141
+ The [caller composition proof](docs/INTEGRATION_PROOF.md) documents the optional
142
+ registry consumer, copied figure inputs, immutable geometry captures and the
143
+ limits of open-surface integration evidence.
144
+
141
145
  ### Randomness has two jobs
142
146
 
143
147
  - `rng.gen` defines stable world content: terrain, layouts, initial spawns.
@@ -0,0 +1,121 @@
1
+ # Caller composition proof
2
+
3
+ This proof composes existing entities in one caller-owned Koota World. It is an
4
+ application example and a bounded test oracle, not another runtime adapter or
5
+ geometry backend. The package root gains no model, renderer, AI or geometry
6
+ dependency.
7
+
8
+ ## Inputs and execution
9
+
10
+ `examples/coordination-proof/consumer-package.json` declares every optional
11
+ package used by the independent registry consumer. Copy it into a private
12
+ directory outside this workspace, copy `registry-import.mjs` beside its
13
+ `package.json`, install it with `npm install --ignore-scripts --bin-links=false`,
14
+ and retain the resulting package lock. The runner resolves all package imports
15
+ from that directory. It never imports this checkout's library source or build.
16
+ The pinned Koota Kit version must include both the coordination and spatial
17
+ exports; coordination alone is insufficient.
18
+
19
+ Supply an authorized copied figure fixture directory containing `fixture.json`
20
+ and the arrays named by its manifest. The loader reads only the rest positions,
21
+ rest normals, full and visible indices, and both poses' positions and normals.
22
+ It checks every byte length and SHA256, decodes little-endian values explicitly,
23
+ checks triangle ranges, and maps visible triangles to their full source face IDs.
24
+ Posed positions already contain their single ground lift; the proof applies no
25
+ additional lift or pose evaluation.
26
+
27
+ The preparation command needs no optional package:
28
+
29
+ ```sh
30
+ node examples/coordination-proof/prepare.mjs "$FIXTURE_DIRECTORY" "$RECEIPT_FILE"
31
+ ```
32
+
33
+ The registry composition command uses the independent installed consumer:
34
+
35
+ ```sh
36
+ node examples/coordination-proof/run.mjs \
37
+ "$FIXTURE_DIRECTORY" "$CONSUMER_DIRECTORY" "$RECEIPT_FILE"
38
+ ```
39
+
40
+ Run these bounded array/query checks through the project's authorized remote
41
+ run facility. Use canonical absolute input paths and a regular-file-only
42
+ consumer install. Declare exactly the copied fixture inputs and private consumer
43
+ inputs, preserve their hashes, and return the receipt directory. Keep logs
44
+ outside returned directories so artifact replacement cannot overwrite them.
45
+
46
+ ## Ownership and observation
47
+
48
+ Model A uses the published Strata terrain API's portable indexed heightfield.
49
+ Its anchor is explicitly authored by the caller on a generated vertex; the proof
50
+ does not claim an unexported Strata mesh or anchor API. Model B uses actual copied
51
+ figure and garment arrays, with full body triangles preserved even when hidden
52
+ by visible outfit masking. These inputs certify open surfaces only.
53
+
54
+ Both existing entities retain caller Position, Velocity, Intent and ModelKind
55
+ traits. The caller writes positions, pulls them through Yuka's existing
56
+ `AIBridge.syncFromKoota`, writes an intent stamped with the consumed surface
57
+ epochs, runs its behavior, then uses `syncToKoota` to write velocity. The bridge
58
+ does not become a second position writer. React's existing `WorldProvider`
59
+ observes that exact raw World, and `useSyncExternalStore` observes the immutable
60
+ view selected by the same committed coordination snapshot.
61
+
62
+ The caller's resource ledger prepares copied immutable geometry versions before
63
+ `publishBatch`. A committed coordination snapshot selects one version for every
64
+ model using its owned registration lifetime and completed geometry, pose and
65
+ transform tuple. Geometry, topology, pose and transform remain explicit spatial
66
+ epochs. Replacing topology must also advance geometry; a publication generation
67
+ or opaque registration token never stands in for a producer epoch.
68
+
69
+ Native raw trait callbacks select the previous completed cohort; coordination
70
+ subscribers select the new completed cohort. Failed preparation or a raw-write
71
+ failure retains the old selected version. A listener exception occurs after
72
+ commit and retains the new selected version. Identical publication is a no-op;
73
+ changed bytes or provenance under the same completed tuple are rejected.
74
+ Pending owned models invalidate the view until every model has a completed
75
+ retained version. A completed model with an explicit empty part list represents
76
+ known absence. Invalid views remain stable for React and fail closed in queries.
77
+
78
+ The private proof can reuse a selected part's visible-to-full face correspondence
79
+ after validating fresh copies and comparing its explicit topology epoch, source,
80
+ instance, part, topology IDs and exact index bytes. Position copies, transforms,
81
+ bounds and fingerprints are still recomputed. A changed selection recomputes the
82
+ correspondence; retained captures keep their prior face ordinals.
83
+
84
+ `capture()` returns a fixed provider, oracle and idempotent `release()`. Always
85
+ release in `finally`. A capture retains its old copied arrays through later
86
+ publications and disposal. Oracle lookup uses the complete copied record stamp,
87
+ including source, instance, part, provider generation, frame, topology and all
88
+ four epochs. Unrelated entries in the binding do not invalidate owned versions.
89
+ Disposal attempts every owned registration, preserves entities and custom state,
90
+ and keeps failed cleanup retryable without touching a replacement registration.
91
+
92
+ ## Evidence boundary
93
+
94
+ The reference oracle exhaustively evaluates indexed triangles in its supported
95
+ numerical range after transforming all vertices into the declared world Euclidean
96
+ metric. It returns stamped `unknown` for overflow, underflow, subnormal arithmetic
97
+ or thin triangles whose squared aspect ratio falls below its roundoff margin.
98
+ It does not
99
+ convert a local nearest winner into a world answer. Witness diagnostics retain
100
+ full source face IDs, barycentric coordinates and geometric normals; degenerate
101
+ faces retain segment/point witnesses without inventing a normal. This is a
102
+ bounded correctness oracle, not a performance backend or a production kernel.
103
+
104
+ The tests exercise checksum rejection, full/visible face ownership, a
105
+ nonuniform transform that changes the winning triangle, coherent two-model
106
+ publication, rollback, postcommit notification errors, explicit invalidation,
107
+ capture retention, registration reuse, reentry and retryable cleanup. New gates
108
+ require an observed fault-injection failure, byte-identical input/source
109
+ restoration, then a passing rerun.
110
+
111
+ Copied input proof, registry composition and headless mounted React observation
112
+ do not establish a live Humanoid producer contract or CPU-to-GPU vertex parity.
113
+ Fresh silent rendered acceptance remains a separate owner-reviewed requirement.
114
+ No containment, solid penetration, traversability or line-of-sight result is
115
+ claimed. The receipts report bounded measurements without a comparative
116
+ performance claim.
117
+
118
+ The pinned 0.8.6 composition uses dense arrays with own indexed entries. The
119
+ prototype-filled sparse-array defect tracked in [issue 68](https://github.com/jbcom/koota-kit/issues/68)
120
+ is outside this legitimate-input proof; this guide does not claim that defect
121
+ has been repaired in the installed release.
@@ -0,0 +1,408 @@
1
+ import { createHash } from "node:crypto";
2
+ import { assertMesh, sourceFaceIds } from "./fixture.mjs";
3
+ import {
4
+ indexedBounds,
5
+ nearestOpenSurface,
6
+ NumericalOracleRangeError,
7
+ worldPositions,
8
+ } from "./open-surface.mjs";
9
+
10
+ export const PROOF_FRAME = Object.freeze({
11
+ id: "caller-proof-world",
12
+ unit: "metre",
13
+ metric: "euclidean",
14
+ });
15
+ const freeze = Object.freeze;
16
+ const tuple = (r) => JSON.stringify([r.geometry, r.pose, r.transform]);
17
+ const stampKey = (r, s) =>
18
+ JSON.stringify([
19
+ r.identity.sourceId,
20
+ r.identity.instanceId,
21
+ r.identity.partId,
22
+ r.identity.registration.id,
23
+ r.identity.registration.generation,
24
+ [r.frame.id, r.frame.unit, r.frame.metric],
25
+ s,
26
+ r.topology[s].id,
27
+ [r.revisions.geometry, r.revisions.topology, r.revisions.pose, r.revisions.transform],
28
+ ]);
29
+ function hashArrays(...values) {
30
+ const hash = createHash("sha256");
31
+ for (const v of values)
32
+ hash.update(
33
+ ArrayBuffer.isView(v) ? Buffer.from(v.buffer, v.byteOffset, v.byteLength) : JSON.stringify(v),
34
+ );
35
+ return hash.digest("hex");
36
+ }
37
+ function sameIndexBytes(left, right) {
38
+ if (left.length !== right.length || left.byteLength !== right.byteLength) return false;
39
+ const a = new Uint8Array(left.buffer, left.byteOffset, left.byteLength);
40
+ const b = new Uint8Array(right.buffer, right.byteOffset, right.byteLength);
41
+ for (let index = 0; index < a.length; index += 1) if (a[index] !== b[index]) return false;
42
+ return true;
43
+ }
44
+
45
+ /** Test-only caller resource ledger; coordination snapshots are the sole commit boundary. */
46
+ export function createProofCohort(coordination, specifications) {
47
+ const models = new Map(),
48
+ views = new WeakMap();
49
+ let busy = false,
50
+ disposalStarted = false;
51
+ function current(m) {
52
+ return (
53
+ coordination.getRegistration(m.id) === m.token && coordination.getEntity(m.id) === m.entity
54
+ );
55
+ }
56
+ function selectedVersion(m, s) {
57
+ const e = s.entries.find((e) => e.id === m.id);
58
+ return e?.completed ? m.versions.get(tuple(e.completed.revisions)) : undefined;
59
+ }
60
+ function collect() {
61
+ if (busy) return;
62
+ const s = coordination.getSnapshot();
63
+ for (const m of models.values()) {
64
+ const active = current(m) ? selectedVersion(m, s) : undefined;
65
+ for (const [id, v] of m.versions) if (v !== active && v.leases === 0) m.versions.delete(id);
66
+ }
67
+ }
68
+ function view(s) {
69
+ function invalid() {
70
+ const cached = views.get(s);
71
+ if (cached?.valid === false) return cached;
72
+ const result = freeze({ coordination: s, records: freeze([]), valid: false });
73
+ views.set(s, result);
74
+ return result;
75
+ }
76
+ if ([...models.values()].some((m) => !m.removed && !current(m))) return invalid();
77
+ // Cached metadata does not lease buffers; verify retained versions before using it.
78
+ if (
79
+ [...models.values()].some(
80
+ (m) =>
81
+ !m.removed &&
82
+ (!s.entries.find((entry) => entry.id === m.id)?.completed || !selectedVersion(m, s)),
83
+ )
84
+ )
85
+ return invalid();
86
+ const cached = views.get(s);
87
+ if (cached?.valid === true) return cached;
88
+ const records = [];
89
+ for (const m of models.values()) {
90
+ if (m.removed) continue;
91
+ const v = selectedVersion(m, s);
92
+ if (s.entries.find((entry) => entry.id === m.id)?.completed && !v) {
93
+ return invalid();
94
+ }
95
+ if (v) records.push(...v.parts.map((p) => p.record));
96
+ }
97
+ records.sort(
98
+ (a, b) =>
99
+ Number(stampKey(a, "full") > stampKey(b, "full")) -
100
+ Number(stampKey(a, "full") < stampKey(b, "full")),
101
+ );
102
+ const result = freeze({ coordination: s, records: freeze(records), valid: true });
103
+ views.set(s, result);
104
+ return result;
105
+ }
106
+ function prepare(m, u) {
107
+ if (!current(m)) throw new Error("proof: stale owned registration lifetime");
108
+ const revisions = freeze({ ...u.revisions }),
109
+ previous = selectedVersion(m, coordination.getSnapshot());
110
+ for (const epoch of ["geometry", "topology", "pose", "transform"]) {
111
+ if (
112
+ !Number.isSafeInteger(revisions[epoch]) ||
113
+ revisions[epoch] < 0 ||
114
+ (previous && revisions[epoch] < previous.revisions[epoch])
115
+ )
116
+ throw new Error(`proof: invalid or regressing ${epoch} epoch`);
117
+ }
118
+ if (
119
+ previous &&
120
+ revisions.topology !== previous.revisions.topology &&
121
+ revisions.geometry === previous.revisions.geometry
122
+ )
123
+ throw new Error("proof: topology replacement must advance geometry epoch");
124
+ const frame = freeze({ ...(u.frame ?? m.frame ?? PROOF_FRAME) });
125
+ if (
126
+ typeof frame.id !== "string" ||
127
+ frame.id.length === 0 ||
128
+ typeof frame.unit !== "string" ||
129
+ frame.unit.length === 0 ||
130
+ frame.metric !== "euclidean"
131
+ )
132
+ throw new Error("proof: invalid declared frame");
133
+ const partIds = new Set();
134
+ const parts = u.parts.map((p) => {
135
+ if (!p.id || partIds.has(p.id) || !p.fullTopologyId || !p.visibleTopologyId)
136
+ throw new Error("proof: invalid or duplicate part/topology identity");
137
+ partIds.add(p.id);
138
+ assertMesh(p.positions, p.full, p.positions.length / 3);
139
+ assertMesh(p.positions, p.visible, p.positions.length / 3);
140
+ const full = new Uint32Array(p.full),
141
+ visible = new Uint32Array(p.visible),
142
+ sourcePositions = new Float32Array(p.positions);
143
+ assertMesh(sourcePositions, full, sourcePositions.length / 3);
144
+ assertMesh(sourcePositions, visible, sourcePositions.length / 3);
145
+ const previousPart = previous?.parts.find((part) => part.record.identity.partId === p.id);
146
+ // Reuse only a committed same-lifetime part after validating fresh copies.
147
+ const canReuse =
148
+ previousPart &&
149
+ previous.revisions.topology === revisions.topology &&
150
+ previousPart.record.identity.sourceId === m.sourceId &&
151
+ previousPart.record.identity.instanceId === m.instanceId &&
152
+ previousPart.record.identity.registration.generation === m.providerGeneration &&
153
+ previousPart.record.topology.full.id === p.fullTopologyId &&
154
+ previousPart.record.topology.visible.id === p.visibleTopologyId &&
155
+ sameIndexBytes(previousPart.full, full) &&
156
+ sameIndexBytes(previousPart.visible, visible);
157
+ const visibleFaces = canReuse ? previousPart.visibleFaces : sourceFaceIds(full, visible),
158
+ positions = worldPositions(sourcePositions, u.toWorld);
159
+ const identity = freeze({
160
+ sourceId: m.sourceId,
161
+ instanceId: m.instanceId,
162
+ partId: p.id,
163
+ registration: freeze({ id: m.id, generation: m.providerGeneration }),
164
+ });
165
+ const record = freeze({
166
+ identity,
167
+ frame,
168
+ revisions,
169
+ topology: freeze({
170
+ full: freeze({
171
+ id: p.fullTopologyId,
172
+ bounds: indexedBounds(positions, full),
173
+ enclosesSurface: true,
174
+ }),
175
+ visible: freeze({
176
+ id: p.visibleTopologyId,
177
+ bounds: indexedBounds(positions, visible),
178
+ enclosesSurface: true,
179
+ }),
180
+ }),
181
+ capabilities: freeze(p.nearestSurface === false ? [] : ["nearest-surface"]),
182
+ });
183
+ return {
184
+ record,
185
+ sourcePositions,
186
+ positions,
187
+ full,
188
+ visible,
189
+ visibleFaces,
190
+ provenance: p.provenance ?? null,
191
+ };
192
+ });
193
+ const topologyHash = hashArrays(
194
+ ...parts.flatMap((p) => [
195
+ p.record.identity,
196
+ p.record.topology.full.id,
197
+ p.record.topology.visible.id,
198
+ p.full,
199
+ p.visible,
200
+ ]),
201
+ );
202
+ if (
203
+ previous &&
204
+ previous.topologyHash !== topologyHash &&
205
+ revisions.geometry === previous.revisions.geometry
206
+ )
207
+ throw new Error("proof: topology bytes/identity replacement must advance geometry epoch");
208
+ const fingerprint = hashArrays(
209
+ revisions,
210
+ [...u.toWorld],
211
+ ...parts.flatMap((p) => [p.record, p.provenance, p.sourcePositions, p.full, p.visible]),
212
+ );
213
+ const existing = m.versions.get(tuple(revisions));
214
+ if (existing) {
215
+ if (existing.fingerprint !== fingerprint)
216
+ throw new Error("proof: changed content under the same completed tuple");
217
+ return existing;
218
+ }
219
+ return { revisions, parts, topologyHash, fingerprint, leases: 0 };
220
+ }
221
+ const specificationIds = new Set();
222
+ for (const specification of specifications) {
223
+ for (const field of ["id", "sourceId", "instanceId"])
224
+ if (typeof specification[field] !== "string" || specification[field].length === 0)
225
+ throw new Error("proof: invalid model identity");
226
+ if (
227
+ !Number.isSafeInteger(specification.providerGeneration) ||
228
+ specification.providerGeneration < 0
229
+ )
230
+ throw new Error("proof: invalid provider generation");
231
+ if (specificationIds.has(specification.id)) throw new Error("proof: duplicate model identity");
232
+ if (coordination.getRegistration(specification.id))
233
+ throw new Error("proof: pre-existing caller registration");
234
+ specificationIds.add(specification.id);
235
+ }
236
+ for (const specification of specifications) {
237
+ if (models.has(specification.id)) throw new Error("proof: duplicate model identity");
238
+ const m = { ...specification, token: undefined, versions: new Map(), removed: false };
239
+ models.set(m.id, m);
240
+ const previousToken = coordination.getRegistration(m.id);
241
+ try {
242
+ m.token = coordination.register(m.entity, { id: m.id, capabilities: ["open-surface"] });
243
+ } catch (error) {
244
+ if (
245
+ coordination.getEntity(m.id) === m.entity &&
246
+ coordination.getRegistration(m.id) !== previousToken
247
+ )
248
+ m.token = coordination.getRegistration(m.id);
249
+ const errors = [error];
250
+ for (const owned of models.values())
251
+ if (owned.token && coordination.getRegistration(owned.id) === owned.token) {
252
+ try {
253
+ coordination.unregister(owned.token);
254
+ } catch (e) {
255
+ errors.push(e);
256
+ }
257
+ }
258
+ throw new AggregateError(
259
+ errors,
260
+ "proof: construction failed; owned token recovery attempted",
261
+ );
262
+ }
263
+ }
264
+ function publish(updates) {
265
+ if (busy) throw new Error("proof: publication/disposal reentry");
266
+ if (disposalStarted) throw new Error("proof: disposed cohort");
267
+ busy = true;
268
+ try {
269
+ const seen = new Set();
270
+ const prepared = updates.map((u) => {
271
+ const m = models.get(u.id);
272
+ if (!m || seen.has(u.id)) throw new Error("proof: unknown or duplicate model update");
273
+ seen.add(u.id);
274
+ return { m, v: prepare(m, u) };
275
+ });
276
+ // Native trait callbacks select OLD; committed subscribers select NEW, from this same ledger.
277
+ for (const { m, v } of prepared) m.versions.set(tuple(v.revisions), v);
278
+ coordination.publishBatch(
279
+ prepared.map(({ m, v }) => ({
280
+ token: m.token,
281
+ completed: {
282
+ revisions: {
283
+ geometry: v.revisions.geometry,
284
+ pose: v.revisions.pose,
285
+ transform: v.revisions.transform,
286
+ },
287
+ },
288
+ })),
289
+ );
290
+ return view(coordination.getSnapshot());
291
+ } finally {
292
+ busy = false;
293
+ collect();
294
+ }
295
+ }
296
+ function capture(snapshot = coordination.getSnapshot()) {
297
+ if (disposalStarted) throw new Error("proof: disposed cohort cannot acquire fresh captures");
298
+ const selected = view(snapshot),
299
+ versions = [],
300
+ partsByStamp = new Map(),
301
+ diagnostics = [];
302
+ if (selected.valid)
303
+ for (const m of models.values()) {
304
+ if (m.removed) continue;
305
+ const v = selectedVersion(m, snapshot);
306
+ if (!v) continue;
307
+ v.leases += 1;
308
+ versions.push(v);
309
+ for (const p of v.parts)
310
+ for (const s of ["full", "visible"]) partsByStamp.set(stampKey(p.record, s), p);
311
+ }
312
+ let released = false;
313
+ const assertLease = () => {
314
+ if (released) throw new Error("proof: released capture");
315
+ };
316
+ return freeze({
317
+ snapshot: selected,
318
+ provider: freeze({
319
+ snapshot() {
320
+ assertLease();
321
+ return selected.valid ? selected.records : [freeze({})];
322
+ },
323
+ }),
324
+ oracle: freeze({
325
+ nearest({ record, topology, point, maximumDistance }) {
326
+ assertLease();
327
+ const p = partsByStamp.get(stampKey(record, topology));
328
+ if (!p) return undefined;
329
+ const consumed = freeze({
330
+ identity: record.identity,
331
+ frame: record.frame,
332
+ topology: freeze({ selection: topology, id: record.topology[topology].id }),
333
+ revisions: record.revisions,
334
+ });
335
+ let hit;
336
+ try {
337
+ hit = nearestOpenSurface(
338
+ p.positions,
339
+ p[topology],
340
+ point,
341
+ topology === "visible" ? p.visibleFaces : undefined,
342
+ );
343
+ } catch (error) {
344
+ if (error instanceof NumericalOracleRangeError)
345
+ return freeze({ kind: "unknown", reason: "numerical-oracle-range", consumed });
346
+ throw error;
347
+ }
348
+ if (!hit || hit.distance > maximumDistance) return freeze({ kind: "no-hit", consumed });
349
+ diagnostics.push(freeze({ identity: record.identity, ...hit, consumed }));
350
+ return freeze({
351
+ kind: "nearest",
352
+ witness: freeze({
353
+ triangleId: `${record.identity.partId}/full-face/${hit.face}`,
354
+ point: hit.point,
355
+ distance: hit.distance,
356
+ frame: record.frame,
357
+ consumed,
358
+ }),
359
+ });
360
+ },
361
+ }),
362
+ getDiagnostics: () => freeze([...diagnostics]),
363
+ release() {
364
+ if (released) return;
365
+ released = true;
366
+ for (const v of versions) v.leases -= 1;
367
+ partsByStamp.clear();
368
+ collect();
369
+ },
370
+ });
371
+ }
372
+ function dispose() {
373
+ if (busy) throw new Error("proof: publication/disposal reentry");
374
+ busy = true;
375
+ disposalStarted = true;
376
+ const errors = [];
377
+ try {
378
+ for (const m of models.values()) {
379
+ if (m.removed) continue;
380
+ if (coordination.getRegistration(m.id) !== m.token) {
381
+ m.removed = true;
382
+ continue;
383
+ }
384
+ try {
385
+ coordination.unregister(m.token);
386
+ } catch (e) {
387
+ errors.push(e);
388
+ }
389
+ m.removed = coordination.getRegistration(m.id) !== m.token;
390
+ }
391
+ } finally {
392
+ busy = false;
393
+ collect();
394
+ }
395
+ if (errors.length)
396
+ throw new AggregateError(
397
+ errors,
398
+ "proof: disposal errors; failed owned cleanup remains retryable",
399
+ );
400
+ }
401
+ return freeze({
402
+ publish,
403
+ capture,
404
+ dispose,
405
+ getSnapshot: () => view(coordination.getSnapshot()),
406
+ subscribe: (listener) => coordination.subscribe(() => listener()),
407
+ });
408
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "koota-caller-proof-consumer",
3
+ "private": true,
4
+ "type": "module",
5
+ "dependencies": {
6
+ "jsdom": "30.1.2",
7
+ "koota": "0.6.6",
8
+ "koota-kit": "0.8.6",
9
+ "react": "19.3.0",
10
+ "react-dom": "19.3.0",
11
+ "strata-procedural": "0.1.0",
12
+ "yuka": "0.7.8",
13
+ "yuka-kit": "1.1.0"
14
+ }
15
+ }