contractcourt: use channel state open channel
What changed, and why it matters
This commit is a straightforward internal code cleanup: it changes the contractcourt package to use a newly introduced chanstate.OpenChannel type instead of the older channeldb.OpenChannel alias. There is no functional change to how the Lightning node operates, and nothing in the commit suggests a security fix or vulnerability.
No security action needed. Treat as normal refactoring/dependency hygiene.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff is a pure refactoring change. It updates import statements and type references across 15 files in contractcourt from channeldb.OpenChannel to chanstate.OpenChannel. The commit message explicitly describes this as moving ownership of the channel state type to the chanstate package while keeping channeldb references for store and error types. No logic, serialization, validation, or behavior changes are present.
Changed components
contractcourt package type referenceschanstate.OpenChannel import usageInspect captured patch +53 / −36
diff --git a/contractcourt/anchor_resolver.go b/contractcourt/anchor_resolver.go
index 35b1760..f0f8df9 100644
--- a/contractcourt/anchor_resolver.go
+++ b/contractcourt/anchor_resolver.go
@@ -10,6 +10,7 @@ import (
"github.com/btcsuite/btcd/chainhash/v2"
"github.com/btcsuite/btcd/wire/v2"
"github.com/lightningnetwork/lnd/channeldb"
+ "github.com/lightningnetwork/lnd/chanstate"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/sweep"
@@ -159,7 +160,7 @@ func (c *anchorResolver) Stop() {
// state required for the proper resolution of a contract.
//
// NOTE: Part of the ContractResolver interface.
-func (c *anchorResolver) SupplementState(state *channeldb.OpenChannel) {
+func (c *anchorResolver) SupplementState(state *chanstate.OpenChannel) {
c.chanType = state.ChanType
}
diff --git a/contractcourt/breach_arbitrator_test.go b/contractcourt/breach_arbitrator_test.go
index c8fa13f..fd09979 100644
--- a/contractcourt/breach_arbitrator_test.go
+++ b/contractcourt/breach_arbitrator_test.go
@@ -22,6 +22,7 @@ import (
"github.com/btcsuite/btcd/wire/v2"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
+ "github.com/lightningnetwork/lnd/chanstate"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
@@ -2315,7 +2316,7 @@ func createInitChannels(t *testing.T) (
binary.BigEndian.Uint64(chanIDBytes[:]),
)
- aliceChannelState := &channeldb.OpenChannel{
+ aliceChannelState := &chanstate.OpenChannel{
LocalChanCfg: aliceCfg,
RemoteChanCfg: bobCfg,
IdentityPub: aliceKeyPub,
@@ -2332,7 +2333,7 @@ func createInitChannels(t *testing.T) (
Db: dbAlice.ChannelStateDB(),
FundingTxn: channels.TestFundingTx,
}
- bobChannelState := &channeldb.OpenChannel{
+ bobChannelState := &chanstate.OpenChannel{
LocalChanCfg: bobCfg,
RemoteChanCfg: aliceCfg,
IdentityPub: bobKeyPub,
diff --git a/contractcourt/breach_resolver.go b/contractcourt/breach_resolver.go
index f341128..29a7f6b 100644
--- a/contractcourt/breach_resolver.go
+++ b/contractcourt/breach_resolver.go
@@ -5,7 +5,7 @@ import (
"fmt"
"io"
- "github.com/lightningnetwork/lnd/channeldb"
+ "github.com/lightningnetwork/lnd/chanstate"
)
// breachResolver is a resolver that will handle breached closes. In the
@@ -88,7 +88,7 @@ func (b *breachResolver) Stop() {
}
// SupplementState adds additional state to the breachResolver.
-func (b *breachResolver) SupplementState(_ *channeldb.OpenChannel) {
+func (b *breachResolver) SupplementState(_ *chanstate.OpenChannel) {
}
// Encode encodes the breachResolver to the passed writer.
diff --git a/contractcourt/chain_arbitrator.go b/contractcourt/chain_arbitrator.go
index 40ef96c..e31b5a9 100644
--- a/contractcourt/chain_arbitrator.go
+++ b/contractcourt/chain_arbitrator.go
@@ -14,6 +14,7 @@ import (
"github.com/lightningnetwork/lnd/chainio"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
+ "github.com/lightningnetwork/lnd/chanstate"
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/graph/db/models"
@@ -331,7 +332,7 @@ var _ chainio.Consumer = (*ChainArbitrator)(nil)
// interact with.
type arbChannel struct {
// channel is the in-memory channel state.
- channel *channeldb.OpenChannel
+ channel *chanstate.OpenChannel
// c references the chain arbitrator and is used by arbChannel
// internally.
@@ -452,7 +453,7 @@ func shouldSuppressClosedChannelNotify(closeType channeldb.ClosureType,
// newActiveChannelArbitrator creates a new instance of an active channel
// arbitrator given the state of the target channel.
-func newActiveChannelArbitrator(channel *channeldb.OpenChannel,
+func newActiveChannelArbitrator(channel *chanstate.OpenChannel,
c *ChainArbitrator, chanEvents *ChainEventSubscription) (*ChannelArbitrator, error) {
// TODO(roasbeef): fetch best height (or pass in) so can ensure block
@@ -518,7 +519,7 @@ func newActiveChannelArbitrator(channel *channeldb.OpenChannel,
tx, c.cfg.ChainHash, &chanPoint, report,
)
},
- FetchHistoricalChannel: func() (*channeldb.OpenChannel, error) {
+ FetchHistoricalChannel: func() (*chanstate.OpenChannel, error) {
chanStateDB := c.chanSource.ChannelStateDB()
return chanStateDB.FetchHistoricalChannel(&chanPoint)
},
@@ -570,7 +571,7 @@ func newActiveChannelArbitrator(channel *channeldb.OpenChannel,
// getArbChannel returns an open channel wrapper for use by channel arbitrators.
func (c *ChainArbitrator) getArbChannel(
- channel *channeldb.OpenChannel) *arbChannel {
+ channel *chanstate.OpenChannel) *arbChannel {
return &arbChannel{
channel: channel,
@@ -878,7 +879,7 @@ func (c *ChainArbitrator) notifyChannelResolved(cp wire.OutPoint) {
// transactions and republish them. This helps ensure propagation of the
// transactions in the event that prior publications failed.
func (c *ChainArbitrator) republishClosingTxs(
- channel *channeldb.OpenChannel) error {
+ channel *chanstate.OpenChannel) error {
// If the channel has had its unilateral close broadcasted already,
// republish it in case it didn't propagate.
@@ -910,7 +911,7 @@ func (c *ChainArbitrator) republishClosingTxs(
//
// NOTE: There is no risk to calling this method if the channel isn't in either
// CommitmentBroadcasted or CoopBroadcasted, but the logs will be misleading.
-func (c *ChainArbitrator) rebroadcast(channel *channeldb.OpenChannel,
+func (c *ChainArbitrator) rebroadcast(channel *chanstate.OpenChannel,
state channeldb.ChannelStatus) error {
chanPoint := channel.FundingOutpoint
@@ -1169,7 +1170,9 @@ func (c *ChainArbitrator) ForceCloseContract(chanPoint wire.OutPoint) (*wire.Msg
// ChannelArbitrator tasked with watching over a new channel. Once a new
// channel has finished its final funding flow, it should be registered with
// the ChainArbitrator so we can properly react to any on-chain events.
-func (c *ChainArbitrator) WatchNewChannel(newChan *channeldb.OpenChannel) error {
+func (c *ChainArbitrator) WatchNewChannel(
+ newChan *chanstate.OpenChannel) error {
+
c.Lock()
defer c.Unlock()
@@ -1454,7 +1457,7 @@ func (c *ChainArbitrator) loadPendingCloseChannels() error {
tx, c.cfg.ChainHash, &chanPoint, report,
)
},
- FetchHistoricalChannel: func() (*channeldb.OpenChannel, error) {
+ FetchHistoricalChannel: func() (*chanstate.OpenChannel, error) {
return chanStateDB.FetchHistoricalChannel(&chanPoint)
},
FindOutgoingHTLCDeadline: func(
diff --git a/contractcourt/chain_arbitrator_test.go b/contractcourt/chain_arbitrator_test.go
index 90d0e4b..582faf4 100644
--- a/contractcourt/chain_arbitrator_test.go
+++ b/contractcourt/chain_arbitrator_test.go
@@ -8,6 +8,7 @@ import (
"github.com/btcsuite/btcd/wire/v2"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
+ "github.com/lightningnetwork/lnd/chanstate"
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/lntest/mock"
@@ -26,7 +27,7 @@ func TestChainArbitratorRepublishCloses(t *testing.T) {
// Create 10 test channels and sync them to the database.
const numChans = 10
- var channels []*channeldb.OpenChannel
+ var channels []*chanstate.OpenChannel
for i := 0; i < numChans; i++ {
lChannel, _, err := lnwallet.CreateTestChannels(
t, channeldb.SingleFunderTweaklessBit,
diff --git a/contractcourt/chain_watcher.go b/contractcourt/chain_watcher.go
index d40bdc5..ee8f870 100644
--- a/contractcourt/chain_watcher.go
+++ b/contractcourt/chain_watcher.go
@@ -20,6 +20,7 @@ import (
"github.com/lightningnetwork/lnd/chainio"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
+ "github.com/lightningnetwork/lnd/chanstate"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lntypes"
@@ -240,7 +241,7 @@ type chainWatcherConfig struct {
// chanState is a snapshot of the persistent state of the channel that
// we're watching. In the event of an on-chain event, we'll query the
// database to ensure that we act using the most up to date state.
- chanState *channeldb.OpenChannel
+ chanState *chanstate.OpenChannel
// notifier is a reference to the channel notifier that we'll use to be
// notified of output spends and when transactions are confirmed.
@@ -658,7 +659,7 @@ type chainSet struct {
// newChainSet creates a new chainSet given the current up to date channel
// state.
-func newChainSet(chanState *channeldb.OpenChannel) (*chainSet, error) {
+func newChainSet(chanState *chanstate.OpenChannel) (*chainSet, error) {
// First, we'll grab the current unrevoked commitments for ourselves
// and the remote party.
localCommit, remoteCommit, err := chanState.LatestCommitments()
@@ -1836,7 +1837,7 @@ func (c *chainWatcher) waitForCommitmentPoint() *btcec.PublicKey {
}
// deriveFundingPkScript derives the script used in the funding output.
-func deriveFundingPkScript(chanState *channeldb.OpenChannel) ([]byte, error) {
+func deriveFundingPkScript(chanState *chanstate.OpenChannel) ([]byte, error) {
localKey := chanState.LocalChanCfg.MultiSigKey.PubKey
remoteKey := chanState.RemoteChanCfg.MultiSigKey.PubKey
diff --git a/contractcourt/chain_watcher_test.go b/contractcourt/chain_watcher_test.go
index d3289b7..27a8102 100644
--- a/contractcourt/chain_watcher_test.go
+++ b/contractcourt/chain_watcher_test.go
@@ -12,6 +12,7 @@ import (
"github.com/lightningnetwork/lnd/chainio"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
+ "github.com/lightningnetwork/lnd/chanstate"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
lnmock "github.com/lightningnetwork/lnd/lntest/mock"
@@ -264,11 +265,11 @@ type dlpTestCase struct {
// state) are returned.
func executeStateTransitions(t *testing.T, htlcAmount lnwire.MilliSatoshi,
aliceChannel, bobChannel *lnwallet.LightningChannel,
- numUpdates uint8) ([]*channeldb.OpenChannel, error) {
+ numUpdates uint8) ([]*chanstate.OpenChannel, error) {
// We'll make a copy of the channel state before each transition.
var (
- chanStates []*channeldb.OpenChannel
+ chanStates []*chanstate.OpenChannel
)
state, err := copyChannelState(t, aliceChannel.State())
diff --git a/contractcourt/channel_arbitrator.go b/contractcourt/channel_arbitrator.go
index 380ccb9..5435b70 100644
--- a/contractcourt/channel_arbitrator.go
+++ b/contractcourt/channel_arbitrator.go
@@ -16,6 +16,7 @@ import (
"github.com/btcsuite/btcd/wire/v2"
"github.com/lightningnetwork/lnd/chainio"
"github.com/lightningnetwork/lnd/channeldb"
+ "github.com/lightningnetwork/lnd/chanstate"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
@@ -166,7 +167,7 @@ type ChannelArbitratorConfig struct {
// FetchHistoricalChannel retrieves the historical state of a channel.
// This is mostly used to supplement the ContractResolvers with
// additional information required for proper contract resolution.
- FetchHistoricalChannel func() (*channeldb.OpenChannel, error)
+ FetchHistoricalChannel func() (*chanstate.OpenChannel, error)
// FindOutgoingHTLCDeadline returns the deadline in absolute block
// height for the specified outgoing HTLC. For an outgoing HTLC, its
@@ -730,7 +731,7 @@ func (c *ChannelArbitrator) relaunchResolvers(commitSet *CommitSet,
// We'll also fetch the historical state of this channel, as it should
// have been marked as closed by now, and supplement it to each resolver
// such that we can properly resolve our pending contracts.
- var chanState *channeldb.OpenChannel
+ var chanState *chanstate.OpenChannel
chanState, err = c.cfg.FetchHistoricalChannel()
switch {
// If we don't find this channel, then it may be the case that it
@@ -2359,7 +2360,7 @@ func (c *ChannelArbitrator) prepContractResolutions(
// We'll also fetch the historical state of this channel, as it should
// have been marked as closed by now, and supplement it to each resolver
// such that we can properly resolve our pending contracts.
- var chanState *channeldb.OpenChannel
+ var chanState *chanstate.OpenChannel
chanState, err := c.cfg.FetchHistoricalChannel()
switch {
// If we don't find this channel, then it may be the case that it
diff --git a/contractcourt/channel_arbitrator_test.go b/contractcourt/channel_arbitrator_test.go
index 4503b2e..12e4619 100644
--- a/contractcourt/channel_arbitrator_test.go
+++ b/contractcourt/channel_arbitrator_test.go
@@ -17,6 +17,7 @@ import (
"github.com/lightningnetwork/lnd/chainio"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
+ "github.com/lightningnetwork/lnd/chanstate"
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/graph/db/models"
@@ -447,8 +448,8 @@ func createTestChannelArbitrator(t *testing.T, log ArbitratorLog,
return nil
},
- FetchHistoricalChannel: func() (*channeldb.OpenChannel, error) {
- return &channeldb.OpenChannel{}, nil
+ FetchHistoricalChannel: func() (*chanstate.OpenChannel, error) {
+ return &chanstate.OpenChannel{}, nil
},
FindOutgoingHTLCDeadline: func(
htlc channeldb.HTLC) fn.Option[int32] {
@@ -2161,7 +2162,9 @@ func TestChannelArbitratorPendingExpiredHTLC(t *testing.T) {
func TestRemoteCloseInitiator(t *testing.T) {
// getCloseSummary returns a unilateral close summary for the channel
// provided.
- getCloseSummary := func(channel *channeldb.OpenChannel) *RemoteUnilateralCloseInfo {
+ getCloseSummary := func(
+ channel *chanstate.OpenChannel) *RemoteUnilateralCloseInfo {
+
return &RemoteUnilateralCloseInfo{
UnilateralCloseSummary: &lnwallet.UnilateralCloseSummary{
SpendDetail: &chainntnfs.SpendDetail{
@@ -2191,7 +2194,7 @@ func TestRemoteCloseInitiator(t *testing.T) {
// is expected to be buffered, as is the default for test
// channel arbitrators.
notifyClose func(sub *ChainEventSubscription,
- channel *channeldb.OpenChannel)
+ channel *chanstate.OpenChannel)
// expectedStates is the set of states we expect the arbitrator
// to progress through.
@@ -2200,7 +2203,7 @@ func TestRemoteCloseInitiator(t *testing.T) {
{
name: "force close",
notifyClose: func(sub *ChainEventSubscription,
- channel *channeldb.OpenChannel) {
+ channel *chanstate.OpenChannel) {
s := getCloseSummary(channel)
sub.RemoteUnilateralClosure <- s
diff --git a/contractcourt/commit_sweep_resolver.go b/contractcourt/commit_sweep_resolver.go
index f47afa5..fe625ec 100644
--- a/contractcourt/commit_sweep_resolver.go
+++ b/contractcourt/commit_sweep_resolver.go
@@ -12,6 +12,7 @@ import (
"github.com/btcsuite/btcd/wire/v2"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
+ "github.com/lightningnetwork/lnd/chanstate"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet"
@@ -210,7 +211,7 @@ func (c *commitSweepResolver) Stop() {
// state required for the proper resolution of a contract.
//
// NOTE: Part of the ContractResolver interface.
-func (c *commitSweepResolver) SupplementState(state *channeldb.OpenChannel) {
+func (c *commitSweepResolver) SupplementState(state *chanstate.OpenChannel) {
if state.ChanType.HasLeaseExpiration() {
c.leaseExpiry = state.ThawHeight
}
diff --git a/contractcourt/contract_resolver.go b/contractcourt/contract_resolver.go
index 62b8f1a..66926b0 100644
--- a/contractcourt/contract_resolver.go
+++ b/contractcourt/contract_resolver.go
@@ -10,6 +10,7 @@ import (
"github.com/btcsuite/btcd/wire/v2"
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/channeldb"
+ "github.com/lightningnetwork/lnd/chanstate"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/sweep"
)
@@ -59,7 +60,7 @@ type ContractResolver interface {
// SupplementState allows the user of a ContractResolver to supplement
// it with state required for the proper resolution of a contract.
- SupplementState(*channeldb.OpenChannel)
+ SupplementState(*chanstate.OpenChannel)
// IsResolved returns true if the stored state in the resolve is fully
// resolved. In this case the target output can be forgotten.
diff --git a/contractcourt/htlc_lease_resolver.go b/contractcourt/htlc_lease_resolver.go
index 438a093..4cda540 100644
--- a/contractcourt/htlc_lease_resolver.go
+++ b/contractcourt/htlc_lease_resolver.go
@@ -3,7 +3,7 @@ package contractcourt
import (
"github.com/btcsuite/btcd/wire/v2"
"github.com/lightningnetwork/lnd/chainntnfs"
- "github.com/lightningnetwork/lnd/channeldb"
+ "github.com/lightningnetwork/lnd/chanstate"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/tlv"
@@ -76,7 +76,7 @@ func (h *htlcLeaseResolver) makeSweepInput(op *wire.OutPoint,
// state required for the proper resolution of a contract.
//
// NOTE: Part of the ContractResolver interface.
-func (h *htlcLeaseResolver) SupplementState(state *channeldb.OpenChannel) {
+func (h *htlcLeaseResolver) SupplementState(state *chanstate.OpenChannel) {
if state.ChanType.HasLeaseExpiration() {
h.leaseExpiry = state.ThawHeight
}
diff --git a/contractcourt/htlc_success_resolver.go b/contractcourt/htlc_success_resolver.go
index 2286ce2..4676412 100644
--- a/contractcourt/htlc_success_resolver.go
+++ b/contractcourt/htlc_success_resolver.go
@@ -11,6 +11,7 @@ import (
"github.com/btcsuite/btcd/txscript/v2"
"github.com/btcsuite/btcd/wire/v2"
"github.com/lightningnetwork/lnd/channeldb"
+ "github.com/lightningnetwork/lnd/chanstate"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/input"
@@ -384,7 +385,7 @@ func (h *htlcSuccessResolver) HtlcPoint() wire.OutPoint {
// production taproot channels after restart.
//
// NOTE: Part of the ContractResolver interface.
-func (h *htlcSuccessResolver) SupplementState(state *channeldb.OpenChannel) {
+func (h *htlcSuccessResolver) SupplementState(state *chanstate.OpenChannel) {
h.htlcLeaseResolver.SupplementState(state)
h.chanType = state.ChanType
}
diff --git a/contractcourt/htlc_timeout_resolver.go b/contractcourt/htlc_timeout_resolver.go
index 5b89a34..e6d20bf 100644
--- a/contractcourt/htlc_timeout_resolver.go
+++ b/contractcourt/htlc_timeout_resolver.go
@@ -12,6 +12,7 @@ import (
"github.com/btcsuite/btcd/wire/v2"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
+ "github.com/lightningnetwork/lnd/chanstate"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lntypes"
@@ -775,7 +776,7 @@ func (h *htlcTimeoutResolver) HtlcPoint() wire.OutPoint {
// production taproot channels after restart.
//
// NOTE: Part of the ContractResolver interface.
-func (h *htlcTimeoutResolver) SupplementState(state *channeldb.OpenChannel) {
+func (h *htlcTimeoutResolver) SupplementState(state *chanstate.OpenChannel) {
h.htlcLeaseResolver.SupplementState(state)
h.chanType = state.ChanType
}
diff --git a/contractcourt/utils_test.go b/contractcourt/utils_test.go
index 27b65b9..aaf1718 100644
--- a/contractcourt/utils_test.go
+++ b/contractcourt/utils_test.go
@@ -10,11 +10,12 @@ import (
"time"
"github.com/lightningnetwork/lnd/channeldb"
+ "github.com/lightningnetwork/lnd/chanstate"
)
// testChannelStateDB extracts the ChannelStateDB from the test channel state.
func testChannelStateDB(t testing.TB,
- state *channeldb.OpenChannel) *channeldb.ChannelStateDB {
+ state *chanstate.OpenChannel) *channeldb.ChannelStateDB {
t.Helper()
@@ -66,8 +67,8 @@ func copyFile(dest, src string) error {
// copyChannelState copies the OpenChannel state by copying the database and
// creating a new struct from it. The copied state is returned.
-func copyChannelState(t *testing.T, state *channeldb.OpenChannel) (
- *channeldb.OpenChannel, error) {
+func copyChannelState(t *testing.T, state *chanstate.OpenChannel) (
+ *chanstate.OpenChannel, error) {
// Make a copy of the DB.
dbFile := filepath.Join(
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.