Adds internal/testkit/world (New, Providers, Person, Org, Workspace,
Product, Rule, Confer, Person.SignIn), internal/testkit/authtest
(NewSessionManager, SignIn) and internal/integrations/fedwiki/fedwikitest
(the environment, tenant, limit and usage, operator root, site rows), and
moves the FedWiki fixtures onto them: newReqEnv, setupQuotaFixture,
newReconcileTestEnv, siblingWorkspace, governFedwikiLimit (Product, Rule,
Confer, no materialization), newSiteEnv, newClaimTestDB (its raw provider
insert goes), seedOperatorSites, newMemberWorkspace, seedUnderstatedQuota,
newTestWorkspace, seedCountedWorkspace and addSiblingWorkspace.
newReconcileDomainsDB and setupReconcileRoot had nothing left to move. The
six sessions the FedWiki web tests build go through authtest. The import
guard covers the three packages, and scripts/deadcode-testonly.txt lists
them.
The six hand-written lock-wait loops become dbtest.AwaitLockWait
(lifecycle_db_test.go, registration_db_test.go, invoice_reconcile_test.go,
stamp_db_test.go, and the two record-swap tests in requests_db_test.go and
swap_record_db_test.go). Backend gains BlockedBy, Query and Abort. The
registration loop is not a verbatim move: its select matches by query text
within the current database and its done check becomes the Abort probe, as
do the two record-swap loops, each keeping its message ("registration
finished (%v) while the first key was held", "the record finished without
writing the activated site's status: %v", "the record finished without
waiting on the parked request: %v"). Kept weaker on purpose: the
never-waited messages are now the helper's, which names the selection
("backend N never waited on a lock" for the pid loops, "no backend waited on
a lock held by backend N" for the record swaps, "no backend running a
statement containing %q waited on a lock" for the registration and invoice
loops), the record-swap and stamp bounds are the helper's ten seconds (were
the tests' thirty), the stamp loop accepts any waiting count (was exactly
one for its pid), and the invoice loop matches the statement text anywhere
in the query (was its prefix). TestDispatchStampsLockRecordsInOneOrder drops
from cognitive complexity 39 to 31; its pin is re-pinned to 31.
Names and results of the three FedWiki packages, internal/integration,
internal/workflows/desiredstate and internal/integrations/stripe/workflows
equal the parent's apart from the new tests (the import guard's forbidden
list with its three subtests, and the lock helper's selection test with its
three subtests and its Abort test) and one change: the three
TestReconcileDomains* tests in internal/integrations/fedwiki now pass on a
freshly reset database. They failed there at the parent, alone and in the
package run, because nothing had registered FedWiki; the fixture now does
(#33).
Fixture equivalence (each fixture run alone on a reset database, every table
of core, fedwiki and domains dumped before and after). Differences, none
read by an assertion: names and addresses carry the builder's shape
(organization "<name> <suffix>", email "<name>-<suffix>@example.test",
subject "sub-<name>-<suffix>"; the display name is the label for tenant
fixtures); the pool is the organization's default pool (name "Default", key
default, auto-managed) where the fixtures wrote a bare one; a workspace now
sits in the organization's default pool through a pool assignment where
newMemberWorkspace, seedOperatorSites and the claim fixtures wrote neither
(seedOperatorSites gains two pools and two assignments); every FedWiki
fixture registers FedWiki and the System tenant, which adds the provider row
(or fills operator_surface_path on the row a claim fixture inserted), the
operations, connection, state and outbox rows, the System tenant's user,
person, 'system' organization type, organization and workspace, and
refreshes the fedwiki_sites resource key; archived sites carry archived_at,
as the status query sets it; sites are minted under a database-minted id;
governing a limit goes through the rule-change function, which adds a
set-change row. Claim tokens are random in both.
25 breaks: 19 failed the old test at the parent and the new test here (one
code break per FedWiki test file in the function it pins, and one per
converted lock wait with the lock removed; the three loops with an Abort
probe fail through it with their own message), and six on the kit alone
failed the new test. No assertion in a test body changed.
Temporal Workflows
This directory contains the Temporal workflow definitions, activity implementations, and worker configuration for the member-console application.
Structure: Domain-Based
We use a Domain-Based (or Feature-Based) structure. Instead of grouping all workflows in one folder and all activities in another, we group them by business domain.
internal/workflows/
├── client.go # Temporal client factory
├── worker.go # Central worker setup & registration
├── queues/
│ └── queues.go # Task Queue constants
├── common/ # Shared utilities & generic activities
│ └── options.go # Standard ActivityOptions (retries, timeouts)
├── example/ # "Example" Domain
│ ├── workflow.go # Workflow definition
│ └── activities.go # Domain-specific activities
├── fedwiki/ # "FedWiki" Domain - Site provisioning
│ ├── README.md # Domain documentation
│ ├── FARMMANAGER.md # FarmManager API documentation
│ ├── workflow.go # Create/Delete site workflows
│ ├── activities.go # Site management activities
│ └── farmmanager_client.go # HTTP client for FarmManager API
└── onboarding/ # (Future) "Onboarding" Domain
├── workflow.go
└── activities.go
Adding a New Domain
- Create a directory:
internal/workflows/<domain_name>/ - Define the Workflow: Create
workflow.goin that directory. - Define Activities: Create
activities.goin that directory.- Define a struct (e.g.,
Activities) to hold dependencies (DB, Logger). - Implement a
NewActivitiesconstructor.
- Define a struct (e.g.,
- Register: Go to
internal/workflows/worker.goand add your new domain to theNewWorkerfunction:
// internal/workflows/worker.go
// ... inside NewWorker ...
w.RegisterWorkflow(yourdomain.Workflow)
w.RegisterActivity(yourdomain.NewActivities(cfg.DB, cfg.Logger))
Usage
Starting the Worker
The worker is embedded in the main application but can be extracted later.
import "git.coopcloud.tech/wiki-cafe/member-console/internal/workflows"
// ... in your main setup ...
workerCfg := workflows.DefaultWorkerConfig(database, logger)
worker, err := workflows.NewWorker(temporalClient, workerCfg)
if err := worker.Start(); err != nil {
// handle error
}
defer worker.Stop()
Starting a Workflow
import (
"git.coopcloud.tech/wiki-cafe/member-console/internal/workflows"
"git.coopcloud.tech/wiki-cafe/member-console/internal/workflows/queues"
"git.coopcloud.tech/wiki-cafe/member-console/internal/workflows/example"
)
// ... inside an HTTP handler ...
workflowOpts := client.StartWorkflowOptions{
ID: "example-" + uuid.New().String(),
TaskQueue: queues.Main,
}
we, err := temporalClient.ExecuteWorkflow(
ctx,
workflowOpts,
example.Workflow, // Pass the function reference
example.Input{Message: "hello"},
)