@almadar/std 1.0.15 → 2.0.0

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.
@@ -4084,6 +4084,943 @@ var GAME_UI_BEHAVIORS = [
4084
4084
  LEVEL_PROGRESS_BEHAVIOR
4085
4085
  ];
4086
4086
 
4087
+ // behaviors/infrastructure.ts
4088
+ var CIRCUIT_BREAKER_BEHAVIOR = {
4089
+ name: "std-circuit-breaker",
4090
+ version: "1.0.0",
4091
+ description: "Circuit breaker pattern with automatic recovery",
4092
+ orbitals: [
4093
+ {
4094
+ name: "CircuitBreakerOrbital",
4095
+ entity: {
4096
+ name: "CircuitBreakerState",
4097
+ persistence: "runtime",
4098
+ fields: [
4099
+ { name: "id", type: "string", required: true },
4100
+ { name: "circuitState", type: "string", default: "closed" },
4101
+ { name: "errorCount", type: "number", default: 0 },
4102
+ { name: "errorRate", type: "number", default: 0 },
4103
+ { name: "successCount", type: "number", default: 0 },
4104
+ { name: "totalCount", type: "number", default: 0 },
4105
+ { name: "lastFailure", type: "number", default: null },
4106
+ { name: "lastSuccess", type: "number", default: null },
4107
+ { name: "errorThreshold", type: "number", default: 5 },
4108
+ { name: "errorRateThreshold", type: "number", default: 0.5 },
4109
+ { name: "resetAfterMs", type: "number", default: 6e4 },
4110
+ { name: "halfOpenMaxAttempts", type: "number", default: 3 },
4111
+ { name: "halfOpenAttempts", type: "number", default: 0 }
4112
+ ]
4113
+ },
4114
+ traits: [
4115
+ {
4116
+ name: "CircuitBreaker",
4117
+ linkedEntity: "CircuitBreakerState",
4118
+ category: "lifecycle",
4119
+ emits: [
4120
+ { event: "CIRCUIT_OPENED", scope: "external" },
4121
+ { event: "CIRCUIT_CLOSED", scope: "external" },
4122
+ { event: "CIRCUIT_HALF_OPEN", scope: "external" }
4123
+ ],
4124
+ stateMachine: {
4125
+ states: [
4126
+ { name: "Closed", isInitial: true },
4127
+ { name: "Open" },
4128
+ { name: "HalfOpen" }
4129
+ ],
4130
+ events: [
4131
+ { key: "RECORD_SUCCESS", name: "Record Success" },
4132
+ { key: "RECORD_FAILURE", name: "Record Failure" },
4133
+ { key: "PROBE", name: "Probe" },
4134
+ { key: "RESET", name: "Reset" }
4135
+ ],
4136
+ transitions: [
4137
+ // Closed: record success
4138
+ {
4139
+ from: "Closed",
4140
+ to: "Closed",
4141
+ event: "RECORD_SUCCESS",
4142
+ effects: [
4143
+ ["set", "@entity.successCount", ["+", "@entity.successCount", 1]],
4144
+ ["set", "@entity.totalCount", ["+", "@entity.totalCount", 1]],
4145
+ ["set", "@entity.lastSuccess", ["time/now"]],
4146
+ ["set", "@entity.errorRate", ["/", "@entity.errorCount", ["math/max", "@entity.totalCount", 1]]]
4147
+ ]
4148
+ },
4149
+ // Closed: record failure, stay closed if under threshold
4150
+ {
4151
+ from: "Closed",
4152
+ to: "Closed",
4153
+ event: "RECORD_FAILURE",
4154
+ guard: ["<", ["+", "@entity.errorCount", 1], "@entity.errorThreshold"],
4155
+ effects: [
4156
+ ["set", "@entity.errorCount", ["+", "@entity.errorCount", 1]],
4157
+ ["set", "@entity.totalCount", ["+", "@entity.totalCount", 1]],
4158
+ ["set", "@entity.lastFailure", ["time/now"]],
4159
+ ["set", "@entity.errorRate", ["/", ["+", "@entity.errorCount", 1], ["math/max", "@entity.totalCount", 1]]]
4160
+ ]
4161
+ },
4162
+ // Closed -> Open: threshold exceeded
4163
+ {
4164
+ from: "Closed",
4165
+ to: "Open",
4166
+ event: "RECORD_FAILURE",
4167
+ guard: [">=", ["+", "@entity.errorCount", 1], "@entity.errorThreshold"],
4168
+ effects: [
4169
+ ["set", "@entity.errorCount", ["+", "@entity.errorCount", 1]],
4170
+ ["set", "@entity.totalCount", ["+", "@entity.totalCount", 1]],
4171
+ ["set", "@entity.lastFailure", ["time/now"]],
4172
+ ["set", "@entity.errorRate", ["/", ["+", "@entity.errorCount", 1], ["math/max", "@entity.totalCount", 1]]],
4173
+ ["emit", "CIRCUIT_OPENED", { errorCount: "@entity.errorCount", errorRate: "@entity.errorRate" }]
4174
+ ]
4175
+ },
4176
+ // Open -> HalfOpen: probe after reset timeout
4177
+ {
4178
+ from: "Open",
4179
+ to: "HalfOpen",
4180
+ event: "PROBE",
4181
+ guard: [">", ["-", ["time/now"], "@entity.lastFailure"], "@entity.resetAfterMs"],
4182
+ effects: [
4183
+ ["set", "@entity.halfOpenAttempts", 0],
4184
+ ["emit", "CIRCUIT_HALF_OPEN", {}]
4185
+ ]
4186
+ },
4187
+ // HalfOpen: success -> close
4188
+ {
4189
+ from: "HalfOpen",
4190
+ to: "Closed",
4191
+ event: "RECORD_SUCCESS",
4192
+ effects: [
4193
+ ["set", "@entity.errorCount", 0],
4194
+ ["set", "@entity.errorRate", 0],
4195
+ ["set", "@entity.halfOpenAttempts", 0],
4196
+ ["set", "@entity.successCount", ["+", "@entity.successCount", 1]],
4197
+ ["set", "@entity.lastSuccess", ["time/now"]],
4198
+ ["emit", "CIRCUIT_CLOSED", {}]
4199
+ ]
4200
+ },
4201
+ // HalfOpen: failure -> back to open
4202
+ {
4203
+ from: "HalfOpen",
4204
+ to: "Open",
4205
+ event: "RECORD_FAILURE",
4206
+ effects: [
4207
+ ["set", "@entity.errorCount", ["+", "@entity.errorCount", 1]],
4208
+ ["set", "@entity.lastFailure", ["time/now"]],
4209
+ ["emit", "CIRCUIT_OPENED", { errorCount: "@entity.errorCount" }]
4210
+ ]
4211
+ },
4212
+ // Reset from any state
4213
+ {
4214
+ from: ["Closed", "Open", "HalfOpen"],
4215
+ to: "Closed",
4216
+ event: "RESET",
4217
+ effects: [
4218
+ ["set", "@entity.errorCount", 0],
4219
+ ["set", "@entity.successCount", 0],
4220
+ ["set", "@entity.totalCount", 0],
4221
+ ["set", "@entity.errorRate", 0],
4222
+ ["set", "@entity.halfOpenAttempts", 0],
4223
+ ["set", "@entity.circuitState", "closed"]
4224
+ ]
4225
+ }
4226
+ ]
4227
+ },
4228
+ ticks: [
4229
+ {
4230
+ name: "probe_half_open",
4231
+ interval: "30000",
4232
+ guard: ["=", "@entity.circuitState", "open"],
4233
+ effects: [["emit", "PROBE"]],
4234
+ description: "Periodically probe to transition from Open to HalfOpen"
4235
+ }
4236
+ ]
4237
+ }
4238
+ ],
4239
+ pages: []
4240
+ }
4241
+ ]
4242
+ };
4243
+ var HEALTH_CHECK_BEHAVIOR = {
4244
+ name: "std-health-check",
4245
+ version: "1.0.0",
4246
+ description: "Tick-based health monitoring with degradation detection",
4247
+ orbitals: [
4248
+ {
4249
+ name: "HealthCheckOrbital",
4250
+ entity: {
4251
+ name: "HealthCheckState",
4252
+ persistence: "runtime",
4253
+ fields: [
4254
+ { name: "id", type: "string", required: true },
4255
+ { name: "healthStatus", type: "string", default: "unknown" },
4256
+ { name: "lastCheck", type: "number", default: null },
4257
+ { name: "lastHealthy", type: "number", default: null },
4258
+ { name: "consecutiveFailures", type: "number", default: 0 },
4259
+ { name: "consecutiveSuccesses", type: "number", default: 0 },
4260
+ { name: "checkIntervalMs", type: "number", default: 3e4 },
4261
+ { name: "degradedThreshold", type: "number", default: 2 },
4262
+ { name: "unhealthyThreshold", type: "number", default: 5 },
4263
+ { name: "recoveryThreshold", type: "number", default: 3 },
4264
+ { name: "totalChecks", type: "number", default: 0 },
4265
+ { name: "totalFailures", type: "number", default: 0 }
4266
+ ]
4267
+ },
4268
+ traits: [
4269
+ {
4270
+ name: "HealthCheck",
4271
+ linkedEntity: "HealthCheckState",
4272
+ category: "lifecycle",
4273
+ emits: [
4274
+ { event: "SERVICE_HEALTHY", scope: "external" },
4275
+ { event: "SERVICE_DEGRADED", scope: "external" },
4276
+ { event: "SERVICE_UNHEALTHY", scope: "external" }
4277
+ ],
4278
+ stateMachine: {
4279
+ states: [
4280
+ { name: "Unknown", isInitial: true },
4281
+ { name: "Healthy" },
4282
+ { name: "Degraded" },
4283
+ { name: "Unhealthy" }
4284
+ ],
4285
+ events: [
4286
+ { key: "CHECK_SUCCESS", name: "Check Success" },
4287
+ { key: "CHECK_FAILURE", name: "Check Failure" },
4288
+ { key: "HEALTH_TICK", name: "Health Tick" },
4289
+ { key: "RESET", name: "Reset" }
4290
+ ],
4291
+ transitions: [
4292
+ // Unknown -> Healthy on first success
4293
+ {
4294
+ from: "Unknown",
4295
+ to: "Healthy",
4296
+ event: "CHECK_SUCCESS",
4297
+ effects: [
4298
+ ["set", "@entity.healthStatus", "healthy"],
4299
+ ["set", "@entity.consecutiveSuccesses", 1],
4300
+ ["set", "@entity.consecutiveFailures", 0],
4301
+ ["set", "@entity.lastCheck", ["time/now"]],
4302
+ ["set", "@entity.lastHealthy", ["time/now"]],
4303
+ ["set", "@entity.totalChecks", ["+", "@entity.totalChecks", 1]],
4304
+ ["emit", "SERVICE_HEALTHY", {}]
4305
+ ]
4306
+ },
4307
+ // Unknown -> Degraded on first failure
4308
+ {
4309
+ from: "Unknown",
4310
+ to: "Degraded",
4311
+ event: "CHECK_FAILURE",
4312
+ effects: [
4313
+ ["set", "@entity.healthStatus", "degraded"],
4314
+ ["set", "@entity.consecutiveFailures", 1],
4315
+ ["set", "@entity.consecutiveSuccesses", 0],
4316
+ ["set", "@entity.lastCheck", ["time/now"]],
4317
+ ["set", "@entity.totalChecks", ["+", "@entity.totalChecks", 1]],
4318
+ ["set", "@entity.totalFailures", ["+", "@entity.totalFailures", 1]],
4319
+ ["emit", "SERVICE_DEGRADED", { consecutiveFailures: 1 }]
4320
+ ]
4321
+ },
4322
+ // Healthy: stay healthy on success
4323
+ {
4324
+ from: "Healthy",
4325
+ to: "Healthy",
4326
+ event: "CHECK_SUCCESS",
4327
+ effects: [
4328
+ ["set", "@entity.consecutiveSuccesses", ["+", "@entity.consecutiveSuccesses", 1]],
4329
+ ["set", "@entity.consecutiveFailures", 0],
4330
+ ["set", "@entity.lastCheck", ["time/now"]],
4331
+ ["set", "@entity.lastHealthy", ["time/now"]],
4332
+ ["set", "@entity.totalChecks", ["+", "@entity.totalChecks", 1]]
4333
+ ]
4334
+ },
4335
+ // Healthy -> Degraded on failure
4336
+ {
4337
+ from: "Healthy",
4338
+ to: "Degraded",
4339
+ event: "CHECK_FAILURE",
4340
+ effects: [
4341
+ ["set", "@entity.healthStatus", "degraded"],
4342
+ ["set", "@entity.consecutiveFailures", 1],
4343
+ ["set", "@entity.consecutiveSuccesses", 0],
4344
+ ["set", "@entity.lastCheck", ["time/now"]],
4345
+ ["set", "@entity.totalChecks", ["+", "@entity.totalChecks", 1]],
4346
+ ["set", "@entity.totalFailures", ["+", "@entity.totalFailures", 1]],
4347
+ ["emit", "SERVICE_DEGRADED", { consecutiveFailures: 1 }]
4348
+ ]
4349
+ },
4350
+ // Degraded: stay degraded on failure (below unhealthy threshold)
4351
+ {
4352
+ from: "Degraded",
4353
+ to: "Degraded",
4354
+ event: "CHECK_FAILURE",
4355
+ guard: ["<", ["+", "@entity.consecutiveFailures", 1], "@entity.unhealthyThreshold"],
4356
+ effects: [
4357
+ ["set", "@entity.consecutiveFailures", ["+", "@entity.consecutiveFailures", 1]],
4358
+ ["set", "@entity.consecutiveSuccesses", 0],
4359
+ ["set", "@entity.lastCheck", ["time/now"]],
4360
+ ["set", "@entity.totalChecks", ["+", "@entity.totalChecks", 1]],
4361
+ ["set", "@entity.totalFailures", ["+", "@entity.totalFailures", 1]]
4362
+ ]
4363
+ },
4364
+ // Degraded -> Unhealthy when threshold exceeded
4365
+ {
4366
+ from: "Degraded",
4367
+ to: "Unhealthy",
4368
+ event: "CHECK_FAILURE",
4369
+ guard: [">=", ["+", "@entity.consecutiveFailures", 1], "@entity.unhealthyThreshold"],
4370
+ effects: [
4371
+ ["set", "@entity.healthStatus", "unhealthy"],
4372
+ ["set", "@entity.consecutiveFailures", ["+", "@entity.consecutiveFailures", 1]],
4373
+ ["set", "@entity.lastCheck", ["time/now"]],
4374
+ ["set", "@entity.totalChecks", ["+", "@entity.totalChecks", 1]],
4375
+ ["set", "@entity.totalFailures", ["+", "@entity.totalFailures", 1]],
4376
+ ["emit", "SERVICE_UNHEALTHY", { consecutiveFailures: ["+", "@entity.consecutiveFailures", 1] }]
4377
+ ]
4378
+ },
4379
+ // Degraded -> Healthy on enough successes
4380
+ {
4381
+ from: "Degraded",
4382
+ to: "Healthy",
4383
+ event: "CHECK_SUCCESS",
4384
+ guard: [">=", ["+", "@entity.consecutiveSuccesses", 1], "@entity.recoveryThreshold"],
4385
+ effects: [
4386
+ ["set", "@entity.healthStatus", "healthy"],
4387
+ ["set", "@entity.consecutiveSuccesses", ["+", "@entity.consecutiveSuccesses", 1]],
4388
+ ["set", "@entity.consecutiveFailures", 0],
4389
+ ["set", "@entity.lastCheck", ["time/now"]],
4390
+ ["set", "@entity.lastHealthy", ["time/now"]],
4391
+ ["set", "@entity.totalChecks", ["+", "@entity.totalChecks", 1]],
4392
+ ["emit", "SERVICE_HEALTHY", {}]
4393
+ ]
4394
+ },
4395
+ // Degraded: stay degraded on success (not enough to recover)
4396
+ {
4397
+ from: "Degraded",
4398
+ to: "Degraded",
4399
+ event: "CHECK_SUCCESS",
4400
+ guard: ["<", ["+", "@entity.consecutiveSuccesses", 1], "@entity.recoveryThreshold"],
4401
+ effects: [
4402
+ ["set", "@entity.consecutiveSuccesses", ["+", "@entity.consecutiveSuccesses", 1]],
4403
+ ["set", "@entity.consecutiveFailures", 0],
4404
+ ["set", "@entity.lastCheck", ["time/now"]],
4405
+ ["set", "@entity.totalChecks", ["+", "@entity.totalChecks", 1]]
4406
+ ]
4407
+ },
4408
+ // Unhealthy: stay unhealthy on failure
4409
+ {
4410
+ from: "Unhealthy",
4411
+ to: "Unhealthy",
4412
+ event: "CHECK_FAILURE",
4413
+ effects: [
4414
+ ["set", "@entity.consecutiveFailures", ["+", "@entity.consecutiveFailures", 1]],
4415
+ ["set", "@entity.lastCheck", ["time/now"]],
4416
+ ["set", "@entity.totalChecks", ["+", "@entity.totalChecks", 1]],
4417
+ ["set", "@entity.totalFailures", ["+", "@entity.totalFailures", 1]]
4418
+ ]
4419
+ },
4420
+ // Unhealthy -> Degraded on first success (recovery begins)
4421
+ {
4422
+ from: "Unhealthy",
4423
+ to: "Degraded",
4424
+ event: "CHECK_SUCCESS",
4425
+ effects: [
4426
+ ["set", "@entity.healthStatus", "degraded"],
4427
+ ["set", "@entity.consecutiveSuccesses", 1],
4428
+ ["set", "@entity.consecutiveFailures", 0],
4429
+ ["set", "@entity.lastCheck", ["time/now"]],
4430
+ ["set", "@entity.totalChecks", ["+", "@entity.totalChecks", 1]],
4431
+ ["emit", "SERVICE_DEGRADED", { recovering: true }]
4432
+ ]
4433
+ },
4434
+ // Reset from any state
4435
+ {
4436
+ from: ["Unknown", "Healthy", "Degraded", "Unhealthy"],
4437
+ to: "Unknown",
4438
+ event: "RESET",
4439
+ effects: [
4440
+ ["set", "@entity.healthStatus", "unknown"],
4441
+ ["set", "@entity.consecutiveFailures", 0],
4442
+ ["set", "@entity.consecutiveSuccesses", 0],
4443
+ ["set", "@entity.totalChecks", 0],
4444
+ ["set", "@entity.totalFailures", 0]
4445
+ ]
4446
+ }
4447
+ ]
4448
+ },
4449
+ ticks: [
4450
+ {
4451
+ name: "periodic_health_check",
4452
+ interval: "@entity.checkIntervalMs",
4453
+ effects: [["emit", "HEALTH_TICK"]],
4454
+ description: "Periodically trigger health check"
4455
+ }
4456
+ ]
4457
+ }
4458
+ ],
4459
+ pages: []
4460
+ }
4461
+ ]
4462
+ };
4463
+ var RATE_LIMITER_BEHAVIOR = {
4464
+ name: "std-rate-limiter",
4465
+ version: "1.0.0",
4466
+ description: "Guard-based rate limiting with sliding window reset",
4467
+ orbitals: [
4468
+ {
4469
+ name: "RateLimiterOrbital",
4470
+ entity: {
4471
+ name: "RateLimiterState",
4472
+ persistence: "runtime",
4473
+ fields: [
4474
+ { name: "id", type: "string", required: true },
4475
+ { name: "requestCount", type: "number", default: 0 },
4476
+ { name: "windowStart", type: "number", default: 0 },
4477
+ { name: "rateLimit", type: "number", default: 60 },
4478
+ { name: "windowMs", type: "number", default: 6e4 },
4479
+ { name: "totalRequests", type: "number", default: 0 },
4480
+ { name: "rejectedRequests", type: "number", default: 0 }
4481
+ ]
4482
+ },
4483
+ traits: [
4484
+ {
4485
+ name: "RateLimiter",
4486
+ linkedEntity: "RateLimiterState",
4487
+ category: "lifecycle",
4488
+ emits: [
4489
+ { event: "RATE_LIMIT_EXCEEDED", scope: "external" }
4490
+ ],
4491
+ stateMachine: {
4492
+ states: [
4493
+ { name: "Active", isInitial: true }
4494
+ ],
4495
+ events: [
4496
+ { key: "REQUEST", name: "Record Request" },
4497
+ { key: "REQUEST_REJECTED", name: "Request Rejected" },
4498
+ { key: "WINDOW_RESET", name: "Window Reset" },
4499
+ { key: "RESET", name: "Full Reset" }
4500
+ ],
4501
+ transitions: [
4502
+ // Request allowed
4503
+ {
4504
+ from: "Active",
4505
+ to: "Active",
4506
+ event: "REQUEST",
4507
+ guard: ["<", "@entity.requestCount", "@entity.rateLimit"],
4508
+ effects: [
4509
+ ["set", "@entity.requestCount", ["+", "@entity.requestCount", 1]],
4510
+ ["set", "@entity.totalRequests", ["+", "@entity.totalRequests", 1]]
4511
+ ]
4512
+ },
4513
+ // Request rejected — over limit
4514
+ {
4515
+ from: "Active",
4516
+ to: "Active",
4517
+ event: "REQUEST",
4518
+ guard: [">=", "@entity.requestCount", "@entity.rateLimit"],
4519
+ effects: [
4520
+ ["set", "@entity.rejectedRequests", ["+", "@entity.rejectedRequests", 1]],
4521
+ ["emit", "RATE_LIMIT_EXCEEDED", {
4522
+ requestCount: "@entity.requestCount",
4523
+ rateLimit: "@entity.rateLimit"
4524
+ }]
4525
+ ]
4526
+ },
4527
+ // Sliding window reset
4528
+ {
4529
+ from: "Active",
4530
+ to: "Active",
4531
+ event: "WINDOW_RESET",
4532
+ effects: [
4533
+ ["set", "@entity.requestCount", 0],
4534
+ ["set", "@entity.windowStart", ["time/now"]]
4535
+ ]
4536
+ },
4537
+ // Full counter reset
4538
+ {
4539
+ from: "Active",
4540
+ to: "Active",
4541
+ event: "RESET",
4542
+ effects: [
4543
+ ["set", "@entity.requestCount", 0],
4544
+ ["set", "@entity.totalRequests", 0],
4545
+ ["set", "@entity.rejectedRequests", 0],
4546
+ ["set", "@entity.windowStart", ["time/now"]]
4547
+ ]
4548
+ }
4549
+ ]
4550
+ },
4551
+ ticks: [
4552
+ {
4553
+ name: "window_reset",
4554
+ interval: "@entity.windowMs",
4555
+ effects: [["emit", "WINDOW_RESET"]],
4556
+ description: "Reset request counter on sliding window expiry"
4557
+ }
4558
+ ]
4559
+ }
4560
+ ],
4561
+ pages: []
4562
+ }
4563
+ ]
4564
+ };
4565
+ var CACHE_ASIDE_BEHAVIOR = {
4566
+ name: "std-cache-aside",
4567
+ version: "1.0.0",
4568
+ description: "Cache-aside pattern with TTL-based freshness and eviction",
4569
+ orbitals: [
4570
+ {
4571
+ name: "CacheAsideOrbital",
4572
+ entity: {
4573
+ name: "CacheEntry",
4574
+ persistence: "runtime",
4575
+ fields: [
4576
+ { name: "id", type: "string", required: true },
4577
+ { name: "cacheKey", type: "string", default: "" },
4578
+ { name: "cachedValue", type: "object", default: null },
4579
+ { name: "cachedAt", type: "number", default: 0 },
4580
+ { name: "ttlMs", type: "number", default: 3e5 },
4581
+ { name: "cacheHits", type: "number", default: 0 },
4582
+ { name: "cacheMisses", type: "number", default: 0 },
4583
+ { name: "isFresh", type: "boolean", default: false },
4584
+ { name: "lastAccessed", type: "number", default: 0 }
4585
+ ]
4586
+ },
4587
+ traits: [
4588
+ {
4589
+ name: "CacheAside",
4590
+ linkedEntity: "CacheEntry",
4591
+ category: "lifecycle",
4592
+ emits: [
4593
+ { event: "CACHE_HIT", scope: "internal" },
4594
+ { event: "CACHE_MISS", scope: "internal" },
4595
+ { event: "CACHE_EVICTED", scope: "internal" }
4596
+ ],
4597
+ stateMachine: {
4598
+ states: [
4599
+ { name: "Empty", isInitial: true },
4600
+ { name: "Fresh" },
4601
+ { name: "Stale" }
4602
+ ],
4603
+ events: [
4604
+ { key: "LOOKUP", name: "Cache Lookup" },
4605
+ { key: "POPULATE", name: "Populate Cache" },
4606
+ { key: "INVALIDATE", name: "Invalidate" },
4607
+ { key: "EVICT", name: "Evict" },
4608
+ { key: "EVICTION_TICK", name: "Eviction Tick" }
4609
+ ],
4610
+ transitions: [
4611
+ // Empty: lookup is a miss
4612
+ {
4613
+ from: "Empty",
4614
+ to: "Empty",
4615
+ event: "LOOKUP",
4616
+ effects: [
4617
+ ["set", "@entity.cacheMisses", ["+", "@entity.cacheMisses", 1]],
4618
+ ["set", "@entity.lastAccessed", ["time/now"]],
4619
+ ["emit", "CACHE_MISS", { key: "@entity.cacheKey" }]
4620
+ ]
4621
+ },
4622
+ // Empty → Fresh: populate after fetch
4623
+ {
4624
+ from: "Empty",
4625
+ to: "Fresh",
4626
+ event: "POPULATE",
4627
+ effects: [
4628
+ ["set", "@entity.cachedValue", "@payload.value"],
4629
+ ["set", "@entity.cacheKey", "@payload.key"],
4630
+ ["set", "@entity.cachedAt", ["time/now"]],
4631
+ ["set", "@entity.isFresh", true]
4632
+ ]
4633
+ },
4634
+ // Fresh: lookup is a hit
4635
+ {
4636
+ from: "Fresh",
4637
+ to: "Fresh",
4638
+ event: "LOOKUP",
4639
+ guard: ["<", ["-", ["time/now"], "@entity.cachedAt"], "@entity.ttlMs"],
4640
+ effects: [
4641
+ ["set", "@entity.cacheHits", ["+", "@entity.cacheHits", 1]],
4642
+ ["set", "@entity.lastAccessed", ["time/now"]],
4643
+ ["emit", "CACHE_HIT", { key: "@entity.cacheKey" }]
4644
+ ]
4645
+ },
4646
+ // Fresh → Stale: TTL expired on lookup
4647
+ {
4648
+ from: "Fresh",
4649
+ to: "Stale",
4650
+ event: "LOOKUP",
4651
+ guard: [">=", ["-", ["time/now"], "@entity.cachedAt"], "@entity.ttlMs"],
4652
+ effects: [
4653
+ ["set", "@entity.isFresh", false],
4654
+ ["set", "@entity.cacheMisses", ["+", "@entity.cacheMisses", 1]],
4655
+ ["set", "@entity.lastAccessed", ["time/now"]],
4656
+ ["emit", "CACHE_MISS", { key: "@entity.cacheKey", reason: "ttl_expired" }]
4657
+ ]
4658
+ },
4659
+ // Stale: lookup is a miss
4660
+ {
4661
+ from: "Stale",
4662
+ to: "Stale",
4663
+ event: "LOOKUP",
4664
+ effects: [
4665
+ ["set", "@entity.cacheMisses", ["+", "@entity.cacheMisses", 1]],
4666
+ ["set", "@entity.lastAccessed", ["time/now"]],
4667
+ ["emit", "CACHE_MISS", { key: "@entity.cacheKey", reason: "stale" }]
4668
+ ]
4669
+ },
4670
+ // Stale → Fresh: re-populate
4671
+ {
4672
+ from: "Stale",
4673
+ to: "Fresh",
4674
+ event: "POPULATE",
4675
+ effects: [
4676
+ ["set", "@entity.cachedValue", "@payload.value"],
4677
+ ["set", "@entity.cachedAt", ["time/now"]],
4678
+ ["set", "@entity.isFresh", true]
4679
+ ]
4680
+ },
4681
+ // Fresh → Fresh: update cache
4682
+ {
4683
+ from: "Fresh",
4684
+ to: "Fresh",
4685
+ event: "POPULATE",
4686
+ effects: [
4687
+ ["set", "@entity.cachedValue", "@payload.value"],
4688
+ ["set", "@entity.cachedAt", ["time/now"]]
4689
+ ]
4690
+ },
4691
+ // Invalidate from any cached state
4692
+ {
4693
+ from: ["Fresh", "Stale"],
4694
+ to: "Empty",
4695
+ event: "INVALIDATE",
4696
+ effects: [
4697
+ ["set", "@entity.cachedValue", null],
4698
+ ["set", "@entity.isFresh", false],
4699
+ ["set", "@entity.cachedAt", 0]
4700
+ ]
4701
+ },
4702
+ // Evict (with event)
4703
+ {
4704
+ from: ["Fresh", "Stale"],
4705
+ to: "Empty",
4706
+ event: "EVICT",
4707
+ effects: [
4708
+ ["set", "@entity.cachedValue", null],
4709
+ ["set", "@entity.isFresh", false],
4710
+ ["set", "@entity.cachedAt", 0],
4711
+ ["emit", "CACHE_EVICTED", { key: "@entity.cacheKey" }]
4712
+ ]
4713
+ },
4714
+ // Eviction tick: evict if stale
4715
+ {
4716
+ from: "Stale",
4717
+ to: "Empty",
4718
+ event: "EVICTION_TICK",
4719
+ effects: [
4720
+ ["set", "@entity.cachedValue", null],
4721
+ ["set", "@entity.isFresh", false],
4722
+ ["set", "@entity.cachedAt", 0],
4723
+ ["emit", "CACHE_EVICTED", { key: "@entity.cacheKey", reason: "ttl_eviction" }]
4724
+ ]
4725
+ }
4726
+ ]
4727
+ },
4728
+ ticks: [
4729
+ {
4730
+ name: "eviction_sweep",
4731
+ interval: "60000",
4732
+ guard: ["and", ["!=", "@entity.cachedAt", 0], [">=", ["-", ["time/now"], "@entity.cachedAt"], "@entity.ttlMs"]],
4733
+ effects: [["emit", "EVICTION_TICK"]],
4734
+ description: "Periodically evict stale cache entries"
4735
+ }
4736
+ ]
4737
+ }
4738
+ ],
4739
+ pages: []
4740
+ }
4741
+ ]
4742
+ };
4743
+ var SAGA_BEHAVIOR = {
4744
+ name: "std-saga",
4745
+ version: "1.0.0",
4746
+ description: "Saga pattern with step-by-step execution and reverse compensation on failure",
4747
+ orbitals: [
4748
+ {
4749
+ name: "SagaOrbital",
4750
+ entity: {
4751
+ name: "SagaState",
4752
+ persistence: "runtime",
4753
+ fields: [
4754
+ { name: "id", type: "string", required: true },
4755
+ { name: "sagaName", type: "string", default: "" },
4756
+ { name: "currentStep", type: "number", default: 0 },
4757
+ { name: "totalSteps", type: "number", default: 0 },
4758
+ { name: "sagaStatus", type: "string", default: "idle" },
4759
+ { name: "completedSteps", type: "array", default: [] },
4760
+ { name: "compensatedSteps", type: "array", default: [] },
4761
+ { name: "failedStep", type: "number", default: -1 },
4762
+ { name: "failureReason", type: "string", default: "" },
4763
+ { name: "startedAt", type: "number", default: 0 },
4764
+ { name: "completedAt", type: "number", default: 0 }
4765
+ ]
4766
+ },
4767
+ traits: [
4768
+ {
4769
+ name: "Saga",
4770
+ linkedEntity: "SagaState",
4771
+ category: "lifecycle",
4772
+ emits: [
4773
+ { event: "SAGA_STARTED", scope: "external" },
4774
+ { event: "SAGA_STEP_COMPLETED", scope: "external" },
4775
+ { event: "SAGA_COMPLETED", scope: "external" },
4776
+ { event: "SAGA_COMPENSATING", scope: "external" },
4777
+ { event: "SAGA_COMPENSATION_DONE", scope: "external" },
4778
+ { event: "SAGA_FAILED", scope: "external" }
4779
+ ],
4780
+ stateMachine: {
4781
+ states: [
4782
+ { name: "Idle", isInitial: true },
4783
+ { name: "Running" },
4784
+ { name: "Compensating" },
4785
+ { name: "Completed" },
4786
+ { name: "Failed" }
4787
+ ],
4788
+ events: [
4789
+ { key: "START_SAGA", name: "Start Saga" },
4790
+ { key: "STEP_SUCCESS", name: "Step Success" },
4791
+ { key: "STEP_FAILURE", name: "Step Failure" },
4792
+ { key: "COMPENSATE_SUCCESS", name: "Compensate Success" },
4793
+ { key: "COMPENSATE_FAILURE", name: "Compensate Failure" },
4794
+ { key: "RESET", name: "Reset" }
4795
+ ],
4796
+ transitions: [
4797
+ // Idle → Running: start the saga
4798
+ {
4799
+ from: "Idle",
4800
+ to: "Running",
4801
+ event: "START_SAGA",
4802
+ effects: [
4803
+ ["set", "@entity.sagaStatus", "running"],
4804
+ ["set", "@entity.currentStep", 0],
4805
+ ["set", "@entity.completedSteps", []],
4806
+ ["set", "@entity.compensatedSteps", []],
4807
+ ["set", "@entity.failedStep", -1],
4808
+ ["set", "@entity.failureReason", ""],
4809
+ ["set", "@entity.startedAt", ["time/now"]],
4810
+ ["emit", "SAGA_STARTED", { sagaName: "@entity.sagaName" }]
4811
+ ]
4812
+ },
4813
+ // Running: step success, more steps remaining
4814
+ {
4815
+ from: "Running",
4816
+ to: "Running",
4817
+ event: "STEP_SUCCESS",
4818
+ guard: ["<", ["+", "@entity.currentStep", 1], "@entity.totalSteps"],
4819
+ effects: [
4820
+ ["set", "@entity.currentStep", ["+", "@entity.currentStep", 1]],
4821
+ ["emit", "SAGA_STEP_COMPLETED", {
4822
+ step: "@entity.currentStep",
4823
+ totalSteps: "@entity.totalSteps"
4824
+ }]
4825
+ ]
4826
+ },
4827
+ // Running → Completed: last step succeeded
4828
+ {
4829
+ from: "Running",
4830
+ to: "Completed",
4831
+ event: "STEP_SUCCESS",
4832
+ guard: [">=", ["+", "@entity.currentStep", 1], "@entity.totalSteps"],
4833
+ effects: [
4834
+ ["set", "@entity.sagaStatus", "completed"],
4835
+ ["set", "@entity.completedAt", ["time/now"]],
4836
+ ["emit", "SAGA_COMPLETED", { sagaName: "@entity.sagaName" }]
4837
+ ]
4838
+ },
4839
+ // Running → Compensating: a step failed
4840
+ {
4841
+ from: "Running",
4842
+ to: "Compensating",
4843
+ event: "STEP_FAILURE",
4844
+ effects: [
4845
+ ["set", "@entity.sagaStatus", "compensating"],
4846
+ ["set", "@entity.failedStep", "@entity.currentStep"],
4847
+ ["emit", "SAGA_COMPENSATING", {
4848
+ failedStep: "@entity.currentStep",
4849
+ sagaName: "@entity.sagaName"
4850
+ }]
4851
+ ]
4852
+ },
4853
+ // Compensating: compensation step succeeded, more to undo
4854
+ {
4855
+ from: "Compensating",
4856
+ to: "Compensating",
4857
+ event: "COMPENSATE_SUCCESS",
4858
+ guard: [">", "@entity.currentStep", 0],
4859
+ effects: [
4860
+ ["set", "@entity.currentStep", ["-", "@entity.currentStep", 1]]
4861
+ ]
4862
+ },
4863
+ // Compensating → Failed: all compensations done (reached step 0)
4864
+ {
4865
+ from: "Compensating",
4866
+ to: "Failed",
4867
+ event: "COMPENSATE_SUCCESS",
4868
+ guard: ["<=", "@entity.currentStep", 0],
4869
+ effects: [
4870
+ ["set", "@entity.sagaStatus", "failed"],
4871
+ ["set", "@entity.completedAt", ["time/now"]],
4872
+ ["emit", "SAGA_COMPENSATION_DONE", { sagaName: "@entity.sagaName" }]
4873
+ ]
4874
+ },
4875
+ // Compensating → Failed: compensation itself failed
4876
+ {
4877
+ from: "Compensating",
4878
+ to: "Failed",
4879
+ event: "COMPENSATE_FAILURE",
4880
+ effects: [
4881
+ ["set", "@entity.sagaStatus", "failed"],
4882
+ ["set", "@entity.completedAt", ["time/now"]],
4883
+ ["emit", "SAGA_FAILED", {
4884
+ sagaName: "@entity.sagaName",
4885
+ reason: "Compensation failed"
4886
+ }]
4887
+ ]
4888
+ },
4889
+ // Reset from terminal states
4890
+ {
4891
+ from: ["Completed", "Failed"],
4892
+ to: "Idle",
4893
+ event: "RESET",
4894
+ effects: [
4895
+ ["set", "@entity.sagaStatus", "idle"],
4896
+ ["set", "@entity.currentStep", 0],
4897
+ ["set", "@entity.completedSteps", []],
4898
+ ["set", "@entity.compensatedSteps", []],
4899
+ ["set", "@entity.failedStep", -1],
4900
+ ["set", "@entity.failureReason", ""]
4901
+ ]
4902
+ }
4903
+ ]
4904
+ },
4905
+ ticks: []
4906
+ }
4907
+ ],
4908
+ pages: []
4909
+ }
4910
+ ]
4911
+ };
4912
+ var METRICS_COLLECTOR_BEHAVIOR = {
4913
+ name: "std-metrics-collector",
4914
+ version: "1.0.0",
4915
+ description: "Tick-based metrics aggregation with periodic flush and reporting",
4916
+ orbitals: [
4917
+ {
4918
+ name: "MetricsCollectorOrbital",
4919
+ entity: {
4920
+ name: "MetricsState",
4921
+ persistence: "runtime",
4922
+ fields: [
4923
+ { name: "id", type: "string", required: true },
4924
+ { name: "counters", type: "object", default: {} },
4925
+ { name: "gauges", type: "object", default: {} },
4926
+ { name: "lastFlush", type: "number", default: 0 },
4927
+ { name: "flushIntervalMs", type: "number", default: 6e4 },
4928
+ { name: "totalFlushes", type: "number", default: 0 },
4929
+ { name: "totalRecorded", type: "number", default: 0 }
4930
+ ]
4931
+ },
4932
+ traits: [
4933
+ {
4934
+ name: "MetricsCollector",
4935
+ linkedEntity: "MetricsState",
4936
+ category: "lifecycle",
4937
+ emits: [
4938
+ { event: "METRICS_REPORT", scope: "external" }
4939
+ ],
4940
+ stateMachine: {
4941
+ states: [
4942
+ { name: "Collecting", isInitial: true }
4943
+ ],
4944
+ events: [
4945
+ { key: "RECORD_COUNTER", name: "Record Counter" },
4946
+ { key: "RECORD_GAUGE", name: "Record Gauge" },
4947
+ { key: "FLUSH", name: "Flush Metrics" },
4948
+ { key: "RESET", name: "Reset All" }
4949
+ ],
4950
+ transitions: [
4951
+ // Record a counter increment
4952
+ {
4953
+ from: "Collecting",
4954
+ to: "Collecting",
4955
+ event: "RECORD_COUNTER",
4956
+ effects: [
4957
+ ["set", "@entity.totalRecorded", ["+", "@entity.totalRecorded", 1]]
4958
+ ]
4959
+ },
4960
+ // Record a gauge value
4961
+ {
4962
+ from: "Collecting",
4963
+ to: "Collecting",
4964
+ event: "RECORD_GAUGE",
4965
+ effects: [
4966
+ ["set", "@entity.totalRecorded", ["+", "@entity.totalRecorded", 1]]
4967
+ ]
4968
+ },
4969
+ // Flush: emit report and reset counters
4970
+ {
4971
+ from: "Collecting",
4972
+ to: "Collecting",
4973
+ event: "FLUSH",
4974
+ effects: [
4975
+ ["emit", "METRICS_REPORT", {
4976
+ counters: "@entity.counters",
4977
+ gauges: "@entity.gauges",
4978
+ totalRecorded: "@entity.totalRecorded"
4979
+ }],
4980
+ ["set", "@entity.counters", {}],
4981
+ ["set", "@entity.lastFlush", ["time/now"]],
4982
+ ["set", "@entity.totalFlushes", ["+", "@entity.totalFlushes", 1]]
4983
+ ]
4984
+ },
4985
+ // Full reset
4986
+ {
4987
+ from: "Collecting",
4988
+ to: "Collecting",
4989
+ event: "RESET",
4990
+ effects: [
4991
+ ["set", "@entity.counters", {}],
4992
+ ["set", "@entity.gauges", {}],
4993
+ ["set", "@entity.totalRecorded", 0],
4994
+ ["set", "@entity.totalFlushes", 0],
4995
+ ["set", "@entity.lastFlush", 0]
4996
+ ]
4997
+ }
4998
+ ]
4999
+ },
5000
+ ticks: [
5001
+ {
5002
+ name: "periodic_flush",
5003
+ interval: "@entity.flushIntervalMs",
5004
+ guard: [">", "@entity.totalRecorded", 0],
5005
+ effects: [["emit", "FLUSH"]],
5006
+ description: "Periodically flush accumulated metrics"
5007
+ }
5008
+ ]
5009
+ }
5010
+ ],
5011
+ pages: []
5012
+ }
5013
+ ]
5014
+ };
5015
+ var INFRASTRUCTURE_BEHAVIORS = [
5016
+ CIRCUIT_BREAKER_BEHAVIOR,
5017
+ HEALTH_CHECK_BEHAVIOR,
5018
+ RATE_LIMITER_BEHAVIOR,
5019
+ CACHE_ASIDE_BEHAVIOR,
5020
+ SAGA_BEHAVIOR,
5021
+ METRICS_COLLECTOR_BEHAVIOR
5022
+ ];
5023
+
4087
5024
  // behaviors/registry.ts
4088
5025
  var STANDARD_BEHAVIORS = [
4089
5026
  ...UI_INTERACTION_BEHAVIORS,
@@ -4092,7 +5029,8 @@ var STANDARD_BEHAVIORS = [
4092
5029
  ...FEEDBACK_BEHAVIORS,
4093
5030
  ...GAME_CORE_BEHAVIORS,
4094
5031
  ...GAME_ENTITY_BEHAVIORS,
4095
- ...GAME_UI_BEHAVIORS
5032
+ ...GAME_UI_BEHAVIORS,
5033
+ ...INFRASTRUCTURE_BEHAVIORS
4096
5034
  ];
4097
5035
  var BEHAVIOR_REGISTRY = STANDARD_BEHAVIORS.reduce(
4098
5036
  (acc, behavior) => {