Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
11bd299
feat: let the config engine sign in as a member and carry on past fai…
hamzahalq Oct 22, 2026
76021df
feat: let a mirror's copy recreate prod's legacy response links
hamzahalq Oct 22, 2026
e5f5296
feat: prod mirror, a staging that copies prod's setup with every valu…
hamzahalq Oct 22, 2026
d027dda
feat: prod mirror takes samples of prod's exchanges, faked in their o…
hamzahalq Oct 22, 2026
04f3d0e
feat: prod mirror answers the calls staging shouldn't make
hamzahalq Oct 22, 2026
ebd3307
feat: prod mirror rehearses prod's arrivals through their own entry p…
hamzahalq Oct 25, 2026
538d1eb
feat: bring prod's items onto a mirror from the list pages
hamzahalq Oct 25, 2026
d44077c
feat: test a subscription against prod from its page
hamzahalq Oct 25, 2026
c1254ca
feat: say on the lists how each item's test against prod went
hamzahalq Oct 25, 2026
87dfb75
refactor: drop the prod mirror's whole-prod endpoints
hamzahalq Oct 26, 2026
cba9d09
refactor: delete the prod mirror's call catcher
hamzahalq Oct 26, 2026
af919cc
feat: bring prod's items without its addresses and secrets, as to-dos
hamzahalq Oct 26, 2026
7645c98
feat: run a mirror test only once it is filled in, and ask about cust…
hamzahalq Oct 26, 2026
d4d3be3
fix: placeholders for addresses Bitween requires; docs for the new flow
hamzahalq Oct 26, 2026
23926a0
feat: shorter bring preview and to-do list, one row per item
hamzahalq Oct 26, 2026
6c7e6a9
Merge releases/r10.0-develop into hamza/feat/prod-mirror
hamzahalq Oct 26, 2026
1fac708
fix: prod mirror review findings
hamzahalq Oct 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ The database schema is created on first start. A new database refuses to start w
| [Configuration](docs/configuration.md) | Every configuration key, runtime setting and Helm value |
| [Deployment](docs/deployment.md) | Docker image, Helm chart, databases, storage and CI/CD |
| [Operations](docs/operations.md) | Monitoring, troubleshooting, logs and data retention |
| [Prod mirror](docs/prod-mirror.md) | Copying a prod's setup onto a staging, every value faked, to try an upgrade there first |
| [Admin UI](docs/admin-ui.md) | A tour of the web interface |
| [API reference](docs/api-reference.md) | REST conventions and every endpoint |
| [Development](docs/development.md) | Local setup, tests, migrations and extending Bitween |
Expand Down
7 changes: 5 additions & 2 deletions SW.Bitween.Api/Controllers/GatewayController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,11 @@ await xchangeService.RunValidator(subscription.ValidatorRef, validatorProperties
var xchangeId = string.IsNullOrEmpty(idempotencyKey)
? null
: await IdempotencyGuard.FindAsync(dbContext, idempotencyScope, idempotencyKey);
// A mirror's rehearsal calls as prod's partner did, under a correlation of its own, and sends
// what the rehearsal before it sent.
var rehearsal = Services.Mirror.MirrorTraffic.CorrelationFor(options, Request.Headers);
// Not for a replayed call: its payload is a repeat by definition, and it is answered already.
var duplicate = xchangeId is null
var duplicate = xchangeId is null && rehearsal is null
? await new DuplicateArrivals(dbContext).CheckAsync(document, partner.Id, xchangeFile)
: null;

Expand All @@ -217,7 +220,7 @@ await xchangeService.RunValidator(subscription.ValidatorRef, validatorProperties

// The sets already read at the top of the request, from the cache.
var xchange = await xchangeService.CreateXchange(subscription, xchangeFile, xchangeReferences.ToArray(),
Guid.NewGuid().ToString("N"), partner, globalAdapterValuesSet);
rehearsal ?? Guid.NewGuid().ToString("N"), partner, globalAdapterValuesSet);
if (resultSync)
{
// Before the save publishes the exchange, so its result can't arrive unannounced. The
Expand Down
9 changes: 9 additions & 0 deletions SW.Bitween.Api/Interfaces/IInfolinkCache.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using SW.Bitween.Domain;
using SW.Bitween.Domain.Gateway;
Expand All @@ -8,6 +9,14 @@ public interface IInfolinkCache
{
public Task<Subscription[]> ListSubscriptionsByDocumentAsync(int documentId);
public Task<BusGatewayRoute[]> ListBusGatewayRoutesByDocumentAsync(int documentId);

/// <summary>
/// The routes, and those of the switched-off gateways in <paramref name="alsoSwitchedOff"/> too: a
/// mirror's copy switches off gateways prod has on, so nothing runs on its own, and its rehearsal's
/// traffic still goes where prod's would.
/// </summary>
public Task<BusGatewayRoute[]> ListBusGatewayRoutesByDocumentAsync(int documentId, IReadOnlySet<int> alsoSwitchedOff) =>
ListBusGatewayRoutesByDocumentAsync(documentId);
public Task<Notifier[]> ListNotifiersAsync();
public Task<NotificationChannel> NotificationChannelByIdAsync(int channelId);

Expand Down
4 changes: 3 additions & 1 deletion SW.Bitween.Api/Resources/ApiGateways/Search.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@
using Microsoft.EntityFrameworkCore;
using SW.Bitween.Domain.Gateway;
using SW.Bitween.Model;
using SW.Bitween.Services.Mirror;

namespace SW.Bitween.Resources.ApiGateways
{
public class Search(BitweenDbContext dbContext, RequestContext requestContext) : ISearchyHandler
public class Search(BitweenDbContext dbContext, RequestContext requestContext, MirrorService mirror) : ISearchyHandler
{
public async Task<object> Handle(SearchyRequest searchyRequest, bool lookup = false, string searchPhrase = null)
{
// Lookup returns only id/name pairs, which pickers across the app rely on;
// the full list is the data, so that's what the view permission covers.
if (!lookup)
await requestContext.EnsurePermission(dbContext, Model.Permissions.ApiGateways.View);
await mirror.FilterAsync(searchyRequest, "apiGateways", requestContext, dbContext);

var query = from gateway in dbContext.Set<ApiGateway>()
select new ApiGatewayRow
Expand Down
4 changes: 3 additions & 1 deletion SW.Bitween.Api/Resources/BusGateways/Search.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,23 @@
using SW.Bitween.Domain;
using SW.Bitween.Domain.Gateway;
using SW.Bitween.Model;
using SW.Bitween.Services.Mirror;
using SW.EfCoreExtensions;
using SW.PrimitiveTypes;
using System.Linq;
using System.Threading.Tasks;

namespace SW.Bitween.Resources.BusGateways
{
public class Search(BitweenDbContext dbContext, RequestContext requestContext) : ISearchyHandler
public class Search(BitweenDbContext dbContext, RequestContext requestContext, MirrorService mirror) : ISearchyHandler
{
public async Task<object> Handle(SearchyRequest searchyRequest, bool lookup = false, string searchPhrase = null)
{
// Lookup returns only id/name pairs, which pickers across the app rely on;
// the full list is the data, so that's what the view permission covers.
if (!lookup)
await requestContext.EnsurePermission(dbContext, Model.Permissions.BusGateways.View);
await mirror.FilterAsync(searchyRequest, "busGateways", requestContext, dbContext);

var documents = dbContext.Set<Document>();
var dataSources = dbContext.Set<Domain.DataSources.DataSource>();
Expand Down
Loading
Loading