@unrdf/kgc-runtime 26.4.2

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.
Files changed (70) hide show
  1. package/IMPLEMENTATION_SUMMARY.json +150 -0
  2. package/PLUGIN_SYSTEM_SUMMARY.json +149 -0
  3. package/README.md +98 -0
  4. package/TRANSACTION_IMPLEMENTATION.json +119 -0
  5. package/capability-map.md +93 -0
  6. package/docs/api-stability.md +269 -0
  7. package/docs/extensions/plugin-development.md +382 -0
  8. package/package.json +40 -0
  9. package/plugins/registry.json +35 -0
  10. package/src/admission-gate.mjs +414 -0
  11. package/src/api-version.mjs +373 -0
  12. package/src/atomic-admission.mjs +310 -0
  13. package/src/bounds.mjs +289 -0
  14. package/src/bulkhead-manager.mjs +280 -0
  15. package/src/capsule.mjs +524 -0
  16. package/src/crdt.mjs +361 -0
  17. package/src/enhanced-bounds.mjs +614 -0
  18. package/src/executor.mjs +73 -0
  19. package/src/freeze-restore.mjs +521 -0
  20. package/src/index.mjs +62 -0
  21. package/src/materialized-views.mjs +371 -0
  22. package/src/merge.mjs +472 -0
  23. package/src/plugin-isolation.mjs +392 -0
  24. package/src/plugin-manager.mjs +441 -0
  25. package/src/projections-api.mjs +336 -0
  26. package/src/projections-cli.mjs +238 -0
  27. package/src/projections-docs.mjs +300 -0
  28. package/src/projections-ide.mjs +278 -0
  29. package/src/receipt.mjs +340 -0
  30. package/src/rollback.mjs +258 -0
  31. package/src/saga-orchestrator.mjs +355 -0
  32. package/src/schemas.mjs +1330 -0
  33. package/src/storage-optimization.mjs +359 -0
  34. package/src/tool-registry.mjs +272 -0
  35. package/src/transaction.mjs +466 -0
  36. package/src/validators.mjs +485 -0
  37. package/src/work-item.mjs +449 -0
  38. package/templates/plugin-template/README.md +58 -0
  39. package/templates/plugin-template/index.mjs +162 -0
  40. package/templates/plugin-template/plugin.json +19 -0
  41. package/test/admission-gate.test.mjs +583 -0
  42. package/test/api-version.test.mjs +74 -0
  43. package/test/atomic-admission.test.mjs +155 -0
  44. package/test/bounds.test.mjs +341 -0
  45. package/test/bulkhead-manager.test.mjs +236 -0
  46. package/test/capsule.test.mjs +625 -0
  47. package/test/crdt.test.mjs +215 -0
  48. package/test/enhanced-bounds.test.mjs +487 -0
  49. package/test/freeze-restore.test.mjs +472 -0
  50. package/test/materialized-views.test.mjs +243 -0
  51. package/test/merge.test.mjs +665 -0
  52. package/test/plugin-isolation.test.mjs +109 -0
  53. package/test/plugin-manager.test.mjs +208 -0
  54. package/test/projections-api.test.mjs +293 -0
  55. package/test/projections-cli.test.mjs +204 -0
  56. package/test/projections-docs.test.mjs +173 -0
  57. package/test/projections-ide.test.mjs +230 -0
  58. package/test/receipt.test.mjs +295 -0
  59. package/test/rollback.test.mjs +132 -0
  60. package/test/saga-orchestrator.test.mjs +279 -0
  61. package/test/schemas.test.mjs +716 -0
  62. package/test/storage-optimization.test.mjs +503 -0
  63. package/test/tool-registry.test.mjs +341 -0
  64. package/test/transaction.test.mjs +189 -0
  65. package/test/validators.test.mjs +463 -0
  66. package/test/work-item.test.mjs +548 -0
  67. package/test/work-item.test.mjs.bak +548 -0
  68. package/var/kgc/test-atomic-log.json +519 -0
  69. package/var/kgc/test-cascading-log.json +145 -0
  70. package/vitest.config.mjs +18 -0
@@ -0,0 +1,373 @@
1
+ /**
2
+ * @file API Version - Semantic versioning and deprecation tracking
3
+ * @module @unrdf/kgc-runtime/api-version
4
+ * @description Manages API versioning with deprecation policy and compatibility checking
5
+ */
6
+
7
+ import { z } from 'zod';
8
+
9
+ /**
10
+ * Current KGC Runtime API version
11
+ */
12
+ export const CURRENT_API_VERSION = '5.0.1';
13
+
14
+ /**
15
+ * API version status
16
+ */
17
+ export const API_STATUS = {
18
+ STABLE: 'stable',
19
+ BETA: 'beta',
20
+ DEPRECATED: 'deprecated',
21
+ REMOVED: 'removed',
22
+ };
23
+
24
+ /**
25
+ * Version metadata schema
26
+ */
27
+ const VersionMetadataSchema = z.object({
28
+ version: z.string().regex(/^\d+\.\d+\.\d+$/),
29
+ status: z.enum(['stable', 'beta', 'deprecated', 'removed']),
30
+ releaseDate: z.string().optional(),
31
+ deprecationDate: z.string().optional(),
32
+ removalDate: z.string().optional(),
33
+ deprecationReason: z.string().optional(),
34
+ migrationGuide: z.string().optional(),
35
+ breakingChanges: z.array(z.string()).optional(),
36
+ });
37
+
38
+ /**
39
+ * API versions registry
40
+ * Each version tracks its status, deprecation, and compatibility
41
+ */
42
+ const API_VERSIONS = [
43
+ {
44
+ version: '5.0.1',
45
+ status: API_STATUS.BETA,
46
+ releaseDate: '2024-12-27',
47
+ breakingChanges: [
48
+ 'Plugin system introduced',
49
+ 'Enhanced isolation and capability management',
50
+ ],
51
+ },
52
+ {
53
+ version: '5.0.0',
54
+ status: API_STATUS.BETA,
55
+ releaseDate: '2024-12-26',
56
+ breakingChanges: [
57
+ 'Work item system refactored',
58
+ 'Receipt format updated to include parent hash',
59
+ ],
60
+ },
61
+ {
62
+ version: '4.0.0',
63
+ status: API_STATUS.DEPRECATED,
64
+ releaseDate: '2024-11-01',
65
+ deprecationDate: '2024-12-01',
66
+ removalDate: '2025-03-01',
67
+ deprecationReason: 'Replaced by v5 with enhanced governance features',
68
+ migrationGuide: 'See docs/migration/v4-to-v5.md',
69
+ },
70
+ {
71
+ version: '3.0.0',
72
+ status: API_STATUS.REMOVED,
73
+ releaseDate: '2024-06-01',
74
+ deprecationDate: '2024-09-01',
75
+ removalDate: '2024-12-01',
76
+ deprecationReason: 'Legacy API completely removed',
77
+ migrationGuide: 'See docs/migration/v3-to-v5.md',
78
+ },
79
+ ];
80
+
81
+ /**
82
+ * Deprecation policy:
83
+ * - APIs are marked deprecated 2 releases before removal
84
+ * - Deprecated APIs remain functional but emit warnings
85
+ * - Removal happens after minimum 3 months deprecation period
86
+ */
87
+ export const DEPRECATION_POLICY = {
88
+ RELEASES_BEFORE_REMOVAL: 2,
89
+ MIN_DEPRECATION_PERIOD_DAYS: 90,
90
+ WARNING_ENABLED: true,
91
+ };
92
+
93
+ /**
94
+ * API Version Manager - Tracks versions, deprecations, and compatibility
95
+ *
96
+ * @example
97
+ * import { APIVersionManager } from '@unrdf/kgc-runtime/api-version';
98
+ * const versionManager = new APIVersionManager();
99
+ * const compatible = versionManager.isCompatible('5.0.0', '5.0.1');
100
+ * console.log(compatible); // true
101
+ */
102
+ export class APIVersionManager {
103
+ constructor() {
104
+ this.versions = new Map();
105
+ this.deprecationWarnings = new Set();
106
+
107
+ // Load versions
108
+ for (const versionData of API_VERSIONS) {
109
+ this.versions.set(versionData.version, versionData);
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Get current API version
115
+ *
116
+ * @returns {string} Current version
117
+ */
118
+ getCurrentVersion() {
119
+ return CURRENT_API_VERSION;
120
+ }
121
+
122
+ /**
123
+ * Get version metadata
124
+ *
125
+ * @param {string} version - Version to query
126
+ * @returns {Object|null} Version metadata or null if not found
127
+ *
128
+ * @example
129
+ * const metadata = versionManager.getVersionMetadata('5.0.1');
130
+ * console.log(metadata.status); // 'beta'
131
+ */
132
+ getVersionMetadata(version) {
133
+ return this.versions.get(version) || null;
134
+ }
135
+
136
+ /**
137
+ * Check if version is deprecated
138
+ *
139
+ * @param {string} version - Version to check
140
+ * @returns {boolean} True if deprecated
141
+ *
142
+ * @example
143
+ * const deprecated = versionManager.isDeprecated('4.0.0');
144
+ * console.log(deprecated); // true
145
+ */
146
+ isDeprecated(version) {
147
+ const metadata = this.getVersionMetadata(version);
148
+ return metadata?.status === API_STATUS.DEPRECATED;
149
+ }
150
+
151
+ /**
152
+ * Check if version is removed
153
+ *
154
+ * @param {string} version - Version to check
155
+ * @returns {boolean} True if removed
156
+ */
157
+ isRemoved(version) {
158
+ const metadata = this.getVersionMetadata(version);
159
+ return metadata?.status === API_STATUS.REMOVED;
160
+ }
161
+
162
+ /**
163
+ * Check if version is stable
164
+ *
165
+ * @param {string} version - Version to check
166
+ * @returns {boolean} True if stable
167
+ */
168
+ isStable(version) {
169
+ const metadata = this.getVersionMetadata(version);
170
+ return metadata?.status === API_STATUS.STABLE;
171
+ }
172
+
173
+ /**
174
+ * Check version compatibility (semver)
175
+ *
176
+ * @param {string} requiredVersion - Required version
177
+ * @param {string} actualVersion - Actual version
178
+ * @returns {boolean} True if compatible
179
+ *
180
+ * @example
181
+ * const compatible = versionManager.isCompatible('5.0.0', '5.0.1');
182
+ * console.log(compatible); // true (patch compatible)
183
+ */
184
+ isCompatible(requiredVersion, actualVersion) {
185
+ const [reqMajor, reqMinor] = requiredVersion.split('.').map(Number);
186
+ const [actMajor, actMinor] = actualVersion.split('.').map(Number);
187
+
188
+ // Major version must match
189
+ if (reqMajor !== actMajor) {
190
+ return false;
191
+ }
192
+
193
+ // Minor version must be >= required
194
+ if (actMinor < reqMinor) {
195
+ return false;
196
+ }
197
+
198
+ return true;
199
+ }
200
+
201
+ /**
202
+ * Emit deprecation warning
203
+ *
204
+ * @param {string} feature - Deprecated feature
205
+ * @param {string} version - Version it was deprecated
206
+ * @param {string} alternative - Recommended alternative
207
+ *
208
+ * @example
209
+ * versionManager.warnDeprecation('oldFunction', '4.0.0', 'Use newFunction instead');
210
+ */
211
+ warnDeprecation(feature, version, alternative) {
212
+ if (!DEPRECATION_POLICY.WARNING_ENABLED) {
213
+ return;
214
+ }
215
+
216
+ const warningKey = `${feature}@${version}`;
217
+
218
+ // Only warn once per feature
219
+ if (this.deprecationWarnings.has(warningKey)) {
220
+ return;
221
+ }
222
+
223
+ console.warn(
224
+ `[DEPRECATION WARNING] ${feature} is deprecated since v${version}. ${alternative}`
225
+ );
226
+
227
+ this.deprecationWarnings.add(warningKey);
228
+ }
229
+
230
+ /**
231
+ * Get all deprecated versions
232
+ *
233
+ * @returns {Array<Object>} Array of deprecated version metadata
234
+ */
235
+ getDeprecatedVersions() {
236
+ return Array.from(this.versions.values()).filter(
237
+ v => v.status === API_STATUS.DEPRECATED
238
+ );
239
+ }
240
+
241
+ /**
242
+ * Get breaking changes for a version
243
+ *
244
+ * @param {string} version - Version to query
245
+ * @returns {Array<string>} Array of breaking changes
246
+ */
247
+ getBreakingChanges(version) {
248
+ const metadata = this.getVersionMetadata(version);
249
+ return metadata?.breakingChanges || [];
250
+ }
251
+
252
+ /**
253
+ * Calculate days until removal
254
+ *
255
+ * @param {string} version - Deprecated version
256
+ * @returns {number|null} Days until removal or null if not deprecated
257
+ */
258
+ getDaysUntilRemoval(version) {
259
+ const metadata = this.getVersionMetadata(version);
260
+
261
+ if (!metadata || !metadata.removalDate) {
262
+ return null;
263
+ }
264
+
265
+ const now = new Date();
266
+ const removalDate = new Date(metadata.removalDate);
267
+ const diffTime = removalDate - now;
268
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
269
+
270
+ return diffDays;
271
+ }
272
+
273
+ /**
274
+ * List all versions
275
+ *
276
+ * @returns {Array<Object>} All version metadata
277
+ */
278
+ listVersions() {
279
+ return Array.from(this.versions.values());
280
+ }
281
+
282
+ /**
283
+ * Add new version
284
+ *
285
+ * @param {Object} versionData - Version metadata
286
+ */
287
+ addVersion(versionData) {
288
+ const validated = VersionMetadataSchema.parse(versionData);
289
+ this.versions.set(validated.version, validated);
290
+ }
291
+
292
+ /**
293
+ * Mark version as deprecated
294
+ *
295
+ * @param {string} version - Version to deprecate
296
+ * @param {Object} options - Deprecation options
297
+ * @param {string} options.reason - Deprecation reason
298
+ * @param {string} options.migrationGuide - Migration guide URL
299
+ * @param {string} options.removalDate - Planned removal date (ISO)
300
+ */
301
+ deprecateVersion(version, options = {}) {
302
+ const metadata = this.getVersionMetadata(version);
303
+
304
+ if (!metadata) {
305
+ throw new Error(`Version not found: ${version}`);
306
+ }
307
+
308
+ metadata.status = API_STATUS.DEPRECATED;
309
+ metadata.deprecationDate = new Date().toISOString().split('T')[0];
310
+ metadata.deprecationReason = options.reason;
311
+ metadata.migrationGuide = options.migrationGuide;
312
+ metadata.removalDate = options.removalDate;
313
+
314
+ this.versions.set(version, metadata);
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Singleton instance
320
+ */
321
+ const versionManager = new APIVersionManager();
322
+
323
+ /**
324
+ * Get the singleton version manager
325
+ *
326
+ * @returns {APIVersionManager} Version manager instance
327
+ */
328
+ export function getVersionManager() {
329
+ return versionManager;
330
+ }
331
+
332
+ /**
333
+ * Check if plugin API version is compatible with runtime
334
+ *
335
+ * @param {string} pluginVersion - Plugin's required API version
336
+ * @returns {boolean} True if compatible
337
+ *
338
+ * @example
339
+ * const compatible = isPluginCompatible('5.0.0');
340
+ * console.log(compatible); // true
341
+ */
342
+ export function isPluginCompatible(pluginVersion) {
343
+ return versionManager.isCompatible(pluginVersion, CURRENT_API_VERSION);
344
+ }
345
+
346
+ /**
347
+ * Validate plugin API version
348
+ *
349
+ * @param {string} pluginVersion - Plugin's API version
350
+ * @throws {Error} If version is incompatible or removed
351
+ */
352
+ export function validatePluginVersion(pluginVersion) {
353
+ if (versionManager.isRemoved(pluginVersion)) {
354
+ throw new Error(
355
+ `Plugin API version ${pluginVersion} has been removed. Please upgrade plugin.`
356
+ );
357
+ }
358
+
359
+ if (!isPluginCompatible(pluginVersion)) {
360
+ throw new Error(
361
+ `Plugin API version ${pluginVersion} is incompatible with runtime ${CURRENT_API_VERSION}`
362
+ );
363
+ }
364
+
365
+ if (versionManager.isDeprecated(pluginVersion)) {
366
+ const daysUntilRemoval = versionManager.getDaysUntilRemoval(pluginVersion);
367
+ versionManager.warnDeprecation(
368
+ `Plugin API version ${pluginVersion}`,
369
+ pluginVersion,
370
+ `Upgrade to ${CURRENT_API_VERSION}. Removal in ${daysUntilRemoval} days.`
371
+ );
372
+ }
373
+ }
@@ -0,0 +1,310 @@
1
+ /**
2
+ * @fileoverview Atomic Capsule Admission using Two-Phase Commit
3
+ * Ensures all-or-nothing capsule admission across conflicts
4
+ *
5
+ * Pattern: Transaction wrapper around merge operations
6
+ */
7
+
8
+ import { z } from 'zod';
9
+ import { TransactionManager } from './transaction.mjs';
10
+ import { mergeCapsules } from './merge.mjs';
11
+ import { RollbackLog } from './rollback.mjs';
12
+
13
+ // ============================================================================
14
+ // Schemas
15
+ // ============================================================================
16
+
17
+ /**
18
+ * Atomic admission request schema
19
+ */
20
+ const AtomicAdmissionRequestSchema = z.object({
21
+ capsules: z.array(z.any()),
22
+ totalOrder: z.object({
23
+ rules: z.array(z.any()),
24
+ default_rule: z.any(),
25
+ }),
26
+ bounds: z.record(z.any()).optional(),
27
+ });
28
+
29
+ /**
30
+ * Atomic admission result schema
31
+ */
32
+ const AtomicAdmissionResultSchema = z.object({
33
+ success: z.boolean(),
34
+ transaction_id: z.string().optional(),
35
+ admitted: z.array(z.string()),
36
+ denied: z.array(z.string()),
37
+ conflict_receipts: z.array(z.any()),
38
+ receipts: z.array(z.any()).optional(),
39
+ errors: z.array(z.string()).optional(),
40
+ });
41
+
42
+ /**
43
+ * @typedef {z.infer<typeof AtomicAdmissionRequestSchema>} AtomicAdmissionRequest
44
+ */
45
+
46
+ /**
47
+ * @typedef {z.infer<typeof AtomicAdmissionResultSchema>} AtomicAdmissionResult
48
+ */
49
+
50
+ // ============================================================================
51
+ // AtomicAdmissionGate Class
52
+ // ============================================================================
53
+
54
+ /**
55
+ * AtomicAdmissionGate - Atomic capsule admission using transactions
56
+ *
57
+ * Features:
58
+ * - All-or-nothing admission (either all capsules admitted or none)
59
+ * - Automatic rollback on conflict
60
+ * - Receipt generation for all operations
61
+ * - Integration with merge conflict resolution
62
+ *
63
+ * @example
64
+ * const gate = new AtomicAdmissionGate();
65
+ * const result = await gate.admitCapsules(capsules, totalOrder);
66
+ * if (result.success) {
67
+ * console.log('All capsules admitted:', result.admitted);
68
+ * } else {
69
+ * console.log('Admission failed, rolled back');
70
+ * }
71
+ */
72
+ export class AtomicAdmissionGate {
73
+ /**
74
+ * @param {Object} options - Configuration options
75
+ * @param {string} options.logPath - Path to rollback log
76
+ */
77
+ constructor(options = {}) {
78
+ /** @type {TransactionManager} */
79
+ this.txManager = new TransactionManager({
80
+ logPath: options.logPath,
81
+ onRollback: (event) => this._handleRollback(event),
82
+ });
83
+
84
+ /** @type {RollbackLog} */
85
+ this.rollbackLog = new RollbackLog(options.logPath);
86
+
87
+ /** @type {Map<string, any>} */
88
+ this.admittedCapsules = new Map();
89
+ }
90
+
91
+ /**
92
+ * Admit capsules atomically
93
+ *
94
+ * @param {any[]} capsules - Capsules to admit
95
+ * @param {any} totalOrder - Conflict resolution rules
96
+ * @param {any} [bounds] - Optional resource bounds
97
+ * @returns {Promise<AtomicAdmissionResult>} Admission result
98
+ */
99
+ async admitCapsules(capsules, totalOrder, bounds = {}) {
100
+ try {
101
+ // Validate input
102
+ const request = AtomicAdmissionRequestSchema.parse({
103
+ capsules,
104
+ totalOrder,
105
+ bounds,
106
+ });
107
+
108
+ // Phase 1: Merge and detect conflicts
109
+ const mergeResult = mergeCapsules(request.capsules, request.totalOrder);
110
+
111
+ // Check if all capsules were admitted (no conflicts)
112
+ if (mergeResult.denied.length > 0) {
113
+ // Some capsules denied - reject entire batch
114
+ return AtomicAdmissionResultSchema.parse({
115
+ success: false,
116
+ admitted: [],
117
+ denied: mergeResult.denied,
118
+ conflict_receipts: mergeResult.conflict_receipts,
119
+ errors: ['Conflict detected - admission denied to maintain atomicity'],
120
+ });
121
+ }
122
+
123
+ // Phase 2: Create transaction for admission
124
+ const operations = mergeResult.admitted.map(capsuleId => {
125
+ const capsule = request.capsules.find(c => c.id === capsuleId);
126
+ return {
127
+ id: `admit_${capsuleId}`,
128
+ type: 'add_capsule',
129
+ data: capsule,
130
+ };
131
+ });
132
+
133
+ const tx = this.txManager.begin(operations);
134
+
135
+ // Phase 3: Prepare transaction
136
+ const prepareResult = await this.txManager.prepare(tx.id);
137
+
138
+ if (!prepareResult.success) {
139
+ // Prepare failed - abort
140
+ return AtomicAdmissionResultSchema.parse({
141
+ success: false,
142
+ admitted: [],
143
+ denied: mergeResult.admitted,
144
+ conflict_receipts: [],
145
+ errors: prepareResult.errors,
146
+ });
147
+ }
148
+
149
+ // Phase 4: Commit transaction
150
+ const commitResult = await this.txManager.commit(tx.id);
151
+
152
+ if (!commitResult.success) {
153
+ // Commit failed - rollback
154
+ await this.txManager.rollback(tx.id);
155
+
156
+ // Log rollback
157
+ await this.rollbackLog.append(
158
+ tx.id,
159
+ prepareResult.undoOps,
160
+ tx.hash
161
+ );
162
+
163
+ return AtomicAdmissionResultSchema.parse({
164
+ success: false,
165
+ admitted: [],
166
+ denied: mergeResult.admitted,
167
+ conflict_receipts: [],
168
+ errors: commitResult.errors,
169
+ });
170
+ }
171
+
172
+ // Success - store admitted capsules
173
+ for (const capsuleId of mergeResult.admitted) {
174
+ const capsule = request.capsules.find(c => c.id === capsuleId);
175
+ this.admittedCapsules.set(capsuleId, capsule);
176
+ }
177
+
178
+ return AtomicAdmissionResultSchema.parse({
179
+ success: true,
180
+ transaction_id: tx.id,
181
+ admitted: mergeResult.admitted,
182
+ denied: [],
183
+ conflict_receipts: mergeResult.conflict_receipts,
184
+ receipts: commitResult.receipts,
185
+ });
186
+ } catch (error) {
187
+ return AtomicAdmissionResultSchema.parse({
188
+ success: false,
189
+ admitted: [],
190
+ denied: capsules.map(c => c.id),
191
+ conflict_receipts: [],
192
+ errors: [error.message],
193
+ });
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Admit single capsule (convenience method)
199
+ *
200
+ * @param {any} capsule - Capsule to admit
201
+ * @param {any} totalOrder - Conflict resolution rules
202
+ * @returns {Promise<AtomicAdmissionResult>} Admission result
203
+ */
204
+ async admitCapsule(capsule, totalOrder) {
205
+ return this.admitCapsules([capsule], totalOrder);
206
+ }
207
+
208
+ /**
209
+ * Rollback a transaction by ID
210
+ *
211
+ * @param {string} transactionId - Transaction ID to rollback
212
+ * @returns {Promise<{success: boolean, operations_undone: number}>} Rollback result
213
+ */
214
+ async rollbackTransaction(transactionId) {
215
+ // Rollback using transaction manager
216
+ const result = await this.txManager.rollback(transactionId);
217
+
218
+ // Remove rolled back capsules from admitted set
219
+ const tx = this.txManager.getTransaction(transactionId);
220
+ if (tx) {
221
+ for (const op of tx.operations) {
222
+ if (op.type === 'add_capsule' && op.data?.id) {
223
+ this.admittedCapsules.delete(op.data.id);
224
+ }
225
+ }
226
+ }
227
+
228
+ return result;
229
+ }
230
+
231
+ /**
232
+ * Get all admitted capsules
233
+ * @returns {any[]} Admitted capsules
234
+ */
235
+ getAdmittedCapsules() {
236
+ return Array.from(this.admittedCapsules.values());
237
+ }
238
+
239
+ /**
240
+ * Check if capsule is admitted
241
+ *
242
+ * @param {string} capsuleId - Capsule ID
243
+ * @returns {boolean} True if admitted
244
+ */
245
+ isAdmitted(capsuleId) {
246
+ return this.admittedCapsules.has(capsuleId);
247
+ }
248
+
249
+ /**
250
+ * Handle rollback event
251
+ * @private
252
+ */
253
+ _handleRollback(event) {
254
+ console.log(`Transaction ${event.transaction_id} rolled back: ${event.operations_undone} operations undone`);
255
+ }
256
+
257
+ /**
258
+ * Reset state (for testing)
259
+ */
260
+ reset() {
261
+ this.txManager.reset();
262
+ this.admittedCapsules.clear();
263
+ }
264
+ }
265
+
266
+ // ============================================================================
267
+ // Cascading Rollback Support
268
+ // ============================================================================
269
+
270
+ /**
271
+ * Rollback transaction and all dependent transactions
272
+ *
273
+ * @param {AtomicAdmissionGate} gate - Admission gate
274
+ * @param {string} transactionId - Root transaction to rollback
275
+ * @returns {Promise<{success: boolean, rolled_back: string[]}>} Rollback result
276
+ */
277
+ export async function cascadingRollback(gate, transactionId) {
278
+ const rolledBack = [];
279
+ const toRollback = [transactionId];
280
+
281
+ while (toRollback.length > 0) {
282
+ const txId = toRollback.pop();
283
+
284
+ // Rollback this transaction
285
+ const result = await gate.rollbackTransaction(txId);
286
+
287
+ if (result.success) {
288
+ rolledBack.push(txId);
289
+
290
+ // Find dependent transactions (those with this tx as parent)
291
+ const allTx = gate.txManager.getAllTransactions();
292
+ const dependents = allTx.filter(tx => tx.parentHash === txId);
293
+
294
+ for (const depTx of dependents) {
295
+ toRollback.push(depTx.id);
296
+ }
297
+ }
298
+ }
299
+
300
+ return {
301
+ success: true,
302
+ rolled_back: rolledBack,
303
+ };
304
+ }
305
+
306
+ // ============================================================================
307
+ // Exports
308
+ // ============================================================================
309
+
310
+ export default AtomicAdmissionGate;