uniform
Build and Publish / BuildAndDeployArm64 (push) Failing after 7s
Build and Publish / BuildAndDeployAmd64 (push) Failing after 7s

This commit is contained in:
2026-07-20 00:13:24 +02:00
parent 83ed477aa4
commit e6f69f500a
14 changed files with 457 additions and 471 deletions
+2 -100
View File
@@ -84,15 +84,7 @@ func loadUCPCartSigner() *ucp.SigningConfig {
return cfg
}
func getCountryFromHost(host string) string {
if strings.Contains(strings.ToLower(host), "-no") {
return "no"
}
if strings.Contains(strings.ToLower(host), "-se") {
return "se"
}
return "se"
}
type MutationContext struct {
VoucherService voucher.Service
@@ -103,62 +95,6 @@ type CartChangeEvent struct {
Mutations []actor.ApplyResult `json:"mutations"`
}
// catalogProjectionCache is the cart's per-pod, cold-start-ready catalog
// projection cache, backed by platform/catalog.ProjectionStore. It consumes the
// catalog.projection_published wire — a full snapshot (begin/chunk/end, framed by
// epoch) plus incremental deltas — into its OWN local .bin and serves catalog
// facts by SKU (price, tax_class, inventory_tracked, image, …) at add-to-cart /
// checkout-reserve decisions. Stock is NOT cached — queried synchronously from
// inventory per docs/inventory-shape.md.
//
// Clustering: the cart runs N replicas. Each pod has its OWN exclusive bus queue
// (rabbit.ListenToPattern declares an exclusive server-named queue, so every pod
// receives the full stream) and its OWN pod-local .bin. It's a replicated read
// cache — no leader, no shared volume; N pods writing one file would corrupt it.
// See docs/catalog-projection.md.
//
// The cart stores the full catalog.Projection (identity transform): its
// add-to-cart overlay (projection_overlay.go) consumes nearly every field, so
// there's nothing to subset. Deletes ride as tombstones in the store's overlay
// (IsDeleted) so a removed SKU isn't re-fetched before the next snapshot folds
// the delete into the base. Cold-grace: an SKU not yet in the base/overlay
// resolves via the existing PRODUCT_BASE_URL HTTP fetch + overlay.
type catalogProjectionCache struct {
store *catalog.ProjectionStore[catalog.Projection]
}
// identityProjection is the cart's mandatory transform — it stores the full
// Projection because the overlay uses almost all of it.
func identityProjection(p catalog.Projection) catalog.Projection { return p }
// newCatalogProjectionCache opens the per-pod store under dir. dir MUST be
// pod-local (container fs / an emptyDir in k8s) — never a shared volume.
func newCatalogProjectionCache(dir string) (*catalogProjectionCache, error) {
s, err := catalog.NewProjectionStore(dir, "cart-projection.bin", identityProjection, nil)
if err != nil {
return nil, err
}
return &catalogProjectionCache{store: s}, nil
}
// HandleFrame feeds one catalog.projection_published event (snapshot frame or
// delta) into the store; framing/epoch ordering live in platform/catalog.
func (c *catalogProjectionCache) HandleFrame(meta map[string]string, payload []byte) error {
return c.store.HandleFrame(meta, payload)
}
func (c *catalogProjectionCache) Get(sku string) (catalog.Projection, bool) { return c.store.Get(sku) }
// IsDeleted reports whether the bus has explicitly deleted sku (overlay
// tombstone) — call sites skip the HTTP fallback for it.
func (c *catalogProjectionCache) IsDeleted(sku string) bool { return c.store.IsDeleted(sku) }
// Len reports cached entries (base + overlay) — debug/log gauge only.
func (c *catalogProjectionCache) Len() int {
_, base, overlay := c.store.Stats()
return base + overlay
}
func main() {
// cartPort is the bare HTTP port. It drives both the local listener and the
@@ -184,8 +120,6 @@ func main() {
promotionService := promotions.NewPromotionService(nil)
//inventoryPubSub := actor.NewPubSub[inventory.InventoryChange]()
rdb := redis.NewClient(&redis.Options{
Addr: redisAddress,
Password: redisPassword,
@@ -228,44 +162,12 @@ func main() {
_, span := tracer.Start(ctx, fmt.Sprintf("Spawn cart id %d", id))
defer span.End()
ret := cart.NewCartGrain(id, time.Now())
// Set baseline lastChange at spawn; replay may update it to last event timestamp.
// inventoryPubSub.Subscribe(ret.HandleInventoryChange)
err := diskStorage.LoadEvents(ctx, id, ret)
// if err == nil && inventoryService != nil {
// refs := make([]*inventory.InventoryReference, 0)
// for _, item := range ret.Items {
// refs = append(refs, &inventory.InventoryReference{
// SKU: inventory.SKU(item.Sku),
// LocationID: getLocationId(item),
// })
// }
// _, span := tracer.Start(ctx, "update inventory")
// defer span.End()
// res, err := inventoryService.GetInventoryBatch(ctx, refs...)
// if err != nil {
// log.Printf("unable to update inventory %v", err)
// } else {
// for _, update := range res {
// for _, item := range ret.Items {
// if matchesSkuAndLocation(update, *item) && update.Quantity != uint32(item.Stock) {
// // maybe apply an update to give visibility to the cart
// item.Stock = uint16(update.Quantity)
// }
// }
// }
// }
// }
return ret, err
},
Destroy: func(grain actor.Grain[cart.CartGrain]) error {
// cart, err := grain.GetCurrentState()
// if err != nil {
// return err
// }
//inventoryPubSub.Unsubscribe(cart.HandleInventoryChange)
return nil
},
SpawnHost: func(host string) (actor.Host[cart.CartGrain], error) {
@@ -411,7 +313,7 @@ func main() {
log.Print("cart: abandonment recovery DISABLED (CART_ABANDONED_DELAY not set or <= 0)")
}
otelShutdown, err := telemetry.SetupOTelSDK(ctx)
otelShutdown, err := telemetry.SetupOTelSDK(ctx, telemetry.LoggerConfigFromEnv())
if err != nil {
log.Fatalf("Unable to start otel %v", err)
}