arch-go.yml
Rama main · réplica de lectura del repositorio original.
version: 1
# Architecture rules for the modular-monolith bounded contexts.
# See docs/adr/0004-modular-monolith-bounded-contexts.md for the rationale.
#
# Strict mode: bootstrap is for production wiring only. Per-context
# internal code may NOT import its own context's bootstrap; tests reach
# for the per-context testkit instead. Cross-context tests import only
# **.<Y>.api or **.<Y>.testkit, never **.<Y>.bootstrap and never
# **.<Y>.internal.**. testkit packages are pinned to their own context
# so cross-context test allowances cannot be exploited as transitive
# paths to other bounded contexts.
dependenciesRules:
# identity context internals may import only identity's own api +
# internals + test-fixture surface, plus platform packages. Other
# contexts that need identity functionality go through identity/api.
- package: "**.identity.internal.**"
shouldOnlyDependsOn:
internal:
- "**.identity.api"
- "**.identity.api.**"
- "**.identity.internal.**"
- "**.identity.testkit"
- "**.attrkeys"
- "**.httpserver"
- "**.secretseal" # shared AES-256-GCM sealer: ssoconfig seals the OIDC client secret with it (issue #496 extracted it)
- "**.hmactoken" # shared v1 HMAC token envelope: satoken builds its service-account access token on it (ADR-0013 generalizes the host-token machinery; shared via internal/ so identity does not import endpoint)
- "**.revocationset" # shared per-replica revocation snapshot: service-account tokens check it as host tokens do (issue #1226)
- "**.testdb"
# endpoint context internals.
- package: "**.endpoint.internal.**"
shouldOnlyDependsOn:
internal:
- "**.endpoint.api"
- "**.endpoint.api.**"
- "**.endpoint.internal.**"
- "**.endpoint.testkit"
- "**.attrkeys"
- "**.httpserver"
- "**.hmactoken" # shared v1 HMAC token envelope: signedtoken builds its host bearer token on it (extracted so satoken can reuse the same primitive without a cross-context import)
- "**.revocationset" # shared per-replica revocation snapshot, which service-account tokens also use (issue #1226)
- "**.identity.api" # identity.api.AuditEvent, encoded into the enrollment.revoke audit entry
# auditoutbox is the shared transactional audit outbox: a revoke commits its audit entry in its own transaction (issue #1225).
- "**.auditoutbox"
- "**.testdb"
# rulecontent context internals (ADR-0021). The eighth context, carved out of rules at issue #766 because rule content acquired
# its own storage and stopped being a projection of the catalog.
#
# Deliberately the NARROWEST allow-list of any context: rulecontent produces rule definitions and evaluates nothing, so it needs
# no other context's api at all. If an entry ever has to be added here, that is worth reading as a signal that evaluation is
# leaking back into content rather than as a missing permission.
- package: "**.rulecontent.internal.**"
shouldOnlyDependsOn:
internal:
- "**.rulecontent.api"
- "**.rulecontent.api.**"
- "**.rulecontent.internal.**"
- "**.sqlhelpers"
- "**.testdb"
# rules context internals. Cross-context test allowance is
# **.detection.testkit (and only testkit); a transitional
# **.detection.bootstrap + **.detection.testharness pair existed in
# earlier revisions and is no longer permitted.
- package: "**.rules.internal.**"
shouldOnlyDependsOn:
internal:
# versionedset is the shared versioned single-row JSON set (issue #1109): one row holding a whole set, replaced under a
# row lock with a conditional version check and a strictly increasing database-clock stamp. The watched-path set and the
# reachable-address set are the same shape in two contexts, and the ordering guarantees are too subtle to keep in two
# copies that can drift.
- "**.versionedset"
# catchup is the shared catch-up policy (issue #1071): one decision about when a host is owed a state again.
- "**.catchup"
# installerscript is the one definition of a package installer script that the agent (which attaches the package's
# signature) and suspicious_exec (which trusts it) must share exactly, or a signature could be attributed to one path
# while the alert names another (issue #1161).
- "**.installerscript"
# auditoutbox is shared server platform code (issue #1070 moved it out of this context so response could use it too);
# detection-config changes and watched-path replacements commit their audit entries through it.
- "**.auditoutbox"
- "**.rules.api"
- "**.rules.api.**"
- "**.rules.internal.**"
- "**.rules.testkit"
- "**.attrkeys"
- "**.httpserver"
# sqlhelpers is shared DB platform code, the same category as httpserver above, and is already allow-listed for the
# detection, response and visibility internals. Rules is simply the context that had not needed it until the
# monitor-match counters wanted the common deadlock retry (#813) rather than a fourth copy of it.
- "**.sqlhelpers"
- "**.endpoint.api"
- "**.identity.api" # identity.api.AuditRecorder for policy.{create,update,delete} audit
- "**.detection.api"
- "**.detection.api.**"
- "**.detection.testkit"
# rulecontent.api is the supplier seam ADR-0021 defines: rulecontent produces rule definitions, rules consumes and
# evaluates them, exactly as rules already consumes detection.api's GraphReader.
- "**.rulecontent.api"
- "**.rulecontent.api.**"
- "**.testdb"
# response context internals.
- package: "**.response.internal.**"
shouldOnlyDependsOn:
internal:
# versionedset is the shared versioned single-row JSON set (issue #1109): one row holding a whole set, replaced under a
# row lock with a conditional version check and a strictly increasing database-clock stamp. The watched-path set and the
# reachable-address set are the same shape in two contexts, and the ordering guarantees are too subtle to keep in two
# copies that can drift.
- "**.versionedset"
# catchup is the shared catch-up policy (issue #1071): one decision about when a host is owed a state again.
- "**.catchup"
# auditoutbox is the shared transactional audit outbox (issue #1070): a containment change commits its audit entry in the
# transaction that records the change and queues its command, rather than recording the row afterwards.
- "**.auditoutbox"
- "**.response.api"
- "**.response.api.**"
- "**.response.internal.**"
- "**.response.testkit"
- "**.attrkeys"
- "**.httpserver"
- "**.endpoint.api"
- "**.identity.api"
- "**.sqlhelpers"
- "**.testdb"
# control is the shared gRPC wire contract for the agent control channel (a platform package like httpserver/sqlhelpers,
# generated from internal/control/control.proto). The response gateway implements its server side.
- "**.control"
# netaddr parses the address notation an operator writes wherever configuration names hosts or ranges (issue #1059). A
# platform package like httpserver: the trusted-proxy list and the reachable-address set are written by the same operator in
# the same notation, and a second parser would eventually disagree with this one about a bare IPv6 literal.
- "**.netaddr"
# detection context internals.
- package: "**.detection.internal.**"
shouldOnlyDependsOn:
internal:
- "**.detection.api"
- "**.detection.api.**"
- "**.detection.internal.**"
- "**.detection.testkit"
- "**.attrkeys"
- "**.httpserver"
- "**.endpoint.api"
- "**.identity.api"
- "**.rules.api"
- "**.visibility.api" # ADR-0015 cutover: intake fans out to EventLog/EventArchive, the processor claims from EventLog, and correlation/evidence read the EventArchive
- "**.sqlhelpers"
- "**.coordination.leader" # MySQL-advisory-lock leader coordinator: pipeline.Runner gates retention + process-TTL on it
- "**.secretseal" # shared AES-256-GCM sealer: the store seals webhook signing secrets, the delivery worker opens them (issue #496)
- "**.testdb"
# observability context internals (issue #374). A sixth bounded context (an amendment to ADR-0004's
# original five) that owns the runtime trace-sampler settings: its store + admin handler. It imports
# the shared sampler infra (internal/observability/tracing) for the Settings domain type, and
# identity.api for the authz chokepoint + audit recorder its admin endpoint funnels through.
- package: "**.server.observability.internal.**"
shouldOnlyDependsOn:
internal:
- "**.server.observability.internal.**"
- "**.observability.tracing"
- "**.identity.api"
- "**.attrkeys"
- "**.httpserver"
- "**.testdb"
# visibility context internals (ADR-0015): ingestion + the event store. The
# MySQL event_queue (EventLog) store depends only on visibility.api; the
# ClickHouse EventArchive lands later and will add httpserver (TimeRange).
- package: "**.visibility.internal.**"
shouldOnlyDependsOn:
internal:
- "**.visibility.api"
- "**.visibility.api.**"
- "**.visibility.internal.**"
- "**.visibility.testkit"
- "**.httpserver" # TimeRange on the EventArchive correlation read
- "**.sqlhelpers"
- "**.testdb"
# The shared otelsql span policy, so the ClickHouse archive traces queries exactly as the MySQL pool does.
- "**.observability.tracing"
# ----- testkit packages -----------------------------------------------------
#
# testkit is a curated test-fixture surface for each bounded context.
# Pinning its dependencies to its own context (plus platform) prevents
# cross-context tests from using testkit as a transitive sneak-in
# path to a third context: even though rules.internal is allowed to
# import detection.testkit, detection.testkit cannot pull in
# identity/endpoint/response. arch-go inspects direct imports only,
# so this defence-in-depth rule is what makes the layered allow-list
# actually airtight.
- package: "**.identity.testkit"
shouldOnlyDependsOn:
internal:
- "**.identity.api"
- "**.identity.api.**"
- "**.identity.bootstrap" # testkit.ApplySchema wraps bootstrap.ApplySchema
- "**.identity.internal.**"
- "**.attrkeys"
- "**.httpserver"
- "**.sqlhelpers"
- "**.testdb"
- package: "**.endpoint.testkit"
shouldOnlyDependsOn:
internal:
- "**.endpoint.api"
- "**.endpoint.api.**"
- "**.endpoint.bootstrap"
- "**.endpoint.internal.**"
- "**.attrkeys"
- "**.httpserver"
- "**.sqlhelpers"
- "**.testdb"
- package: "**.rules.testkit"
shouldOnlyDependsOn:
internal:
- "**.rules.api"
- "**.rules.api.**"
- "**.rules.bootstrap"
- "**.rules.internal.**"
- "**.attrkeys"
- "**.httpserver"
- "**.sqlhelpers"
- "**.testdb"
- package: "**.response.testkit"
shouldOnlyDependsOn:
internal:
- "**.response.api"
- "**.response.api.**"
- "**.response.bootstrap"
- "**.response.internal.**"
- "**.attrkeys"
- "**.httpserver"
- "**.sqlhelpers"
- "**.testdb"
- package: "**.detection.testkit"
shouldOnlyDependsOn:
internal:
- "**.detection.api"
- "**.detection.api.**"
- "**.detection.bootstrap"
- "**.detection.internal.**"
- "**.rules.api" # Replay runs rulesapi.Rule against fixtures
- "**.visibility.api" # ADR-0015: the detection store now requires an EventArchive (correlation + evidence reads)
- "**.visibility.testkit" # MemArchive: the in-memory EventArchive the fixtures seed instead of the dropped MySQL events table
- "**.attrkeys"
- "**.httpserver"
- "**.sqlhelpers"
- "**.testdb"
- package: "**.server.observability.testkit"
shouldOnlyDependsOn:
internal:
- "**.server.observability.bootstrap"
- "**.server.observability.internal.**"
- "**.observability.tracing"
- "**.attrkeys"
- "**.httpserver"
- "**.testdb"
- package: "**.visibility.testkit"
shouldOnlyDependsOn:
internal:
- "**.visibility.api"
- "**.visibility.api.**"
- "**.visibility.bootstrap"
- "**.visibility.internal.**"
- "**.httpserver" # MemArchive mirrors the EventArchive correlation read (TimeRange)
- "**.testdb"
# ----- api/ packages: pure-types, no cross-context internals ----------------
#
# Each <X>/api/ contains domain value types + interface declarations; any
# executable cross-context wiring belongs in bootstrap/ or internal/. The
# rule catches accidents like "rules.api gained a helper that imports
# detection/internal/mysql for convenience" before they ship.
#
# rules/api carries an explicit cross-context exception: it re-exports
# detection/api types (Event, Process, Finding, NullRawJSON, GraphReader,
# TimeRange) as type aliases so the catalog rule files can implement
# rulesapi.Rule.Evaluate without importing detection/api directly. The
# alias is a deliberate design choice; this rule names it.
- package: "**.identity.api"
shouldOnlyDependsOn:
internal:
- "**.identity.api.**"
- "**.attrkeys"
- "**.httpserver"
- "**.sqlhelpers"
- package: "**.endpoint.api"
shouldOnlyDependsOn:
internal:
- "**.endpoint.api.**"
- "**.attrkeys"
- "**.httpserver"
- "**.sqlhelpers"
- package: "**.rules.api"
shouldOnlyDependsOn:
internal:
# catchup is the shared catch-up policy (issue #1071): the watched-path command carries its status already mapped
# onto that vocabulary, by the context that owns the lifecycle, rather than cast from its spelling here.
- "**.catchup"
- "**.rules.api.**"
- "**.detection.api" # alias re-export of detection/api types
- "**.attrkeys"
- "**.httpserver"
- "**.sqlhelpers"
- package: "**.response.api"
shouldOnlyDependsOn:
internal:
# catchup is the shared catch-up policy (issue #1071): Status.Catchup maps this context's lifecycle onto it, here
# rather than at each consumer, because this is the file a rename happens in.
- "**.catchup"
- "**.response.api.**"
- "**.attrkeys"
- "**.httpserver"
- "**.sqlhelpers"
- package: "**.detection.api"
shouldOnlyDependsOn:
internal:
- "**.detection.api.**"
- "**.visibility.api" # Event envelope alias: the canonical type lives in visibility (ADR-0015)
- "**.attrkeys"
- "**.httpserver"
- "**.sqlhelpers"
- package: "**.visibility.api"
shouldOnlyDependsOn:
internal:
- "**.visibility.api.**"
- "**.httpserver" # TimeRange on the EventArchive read; api is pure types/interfaces (no attrkeys/sqlhelpers)
# ----- platform packages: allow-list of acceptable internal deps ------------
#
# Plan §"Platform packages": "Platform packages MUST NOT import any context."
# An earlier revision encoded this as a deny-list (shouldNotDependsOn each
# bounded context); Gemini's review on PR #100 pointed out that the
# deny-list is not future-proof: adding a sixth bounded context would
# require updating every platform rule by hand. The allow-list form
# (shouldOnlyDependsOn the explicit set of allowed internal-graph deps)
# auto-excludes any new context: a future server/<new-context>/ package
# is implicitly forbidden because it isn't named in the allow.
#
# The allow-list enumerates: every other platform package, the repo-level
# internal/ tree (envparse + observability today), and stdlib/third-party
# imports (which arch-go's "internal:" key doesn't restrict; third-party
# imports flow through automatically, no listing needed).
#
# Patterns use full module paths because `**.bootstrap` would otherwise
# also match each context's bootstrap, and `**.testdb` would also match
# testdb/full (the cross-context fixture composer that legitimately
# imports each testkit).
- package: "github.com/rawtrail/rawtrail/server/config"
shouldOnlyDependsOn:
internal: &platform_allow
- "github.com/rawtrail/rawtrail/server/config"
- "github.com/rawtrail/rawtrail/server/bootstrap"
- "github.com/rawtrail/rawtrail/server/httpserver"
- "github.com/rawtrail/rawtrail/server/metrics"
- "github.com/rawtrail/rawtrail/server/attrkeys"
- "github.com/rawtrail/rawtrail/server/apidocs"
- "github.com/rawtrail/rawtrail/server/sqlhelpers"
- "github.com/rawtrail/rawtrail/server/ui"
- "**.internal.**"
- package: "github.com/rawtrail/rawtrail/server/bootstrap"
shouldOnlyDependsOn:
internal: *platform_allow
- package: "github.com/rawtrail/rawtrail/server/httpserver"
shouldOnlyDependsOn:
internal: *platform_allow
- package: "github.com/rawtrail/rawtrail/server/metrics"
shouldOnlyDependsOn:
internal: *platform_allow
- package: "github.com/rawtrail/rawtrail/server/attrkeys"
shouldOnlyDependsOn:
internal: *platform_allow
- package: "github.com/rawtrail/rawtrail/server/apidocs"
shouldOnlyDependsOn:
internal: *platform_allow
- package: "github.com/rawtrail/rawtrail/server/sqlhelpers"
shouldOnlyDependsOn:
internal: *platform_allow
# auditoutbox sits with the platform packages but carries ONE context dependency, deliberately: the audit event it encodes and the
# recorder a drain delivers to are identity's contract (ADR-0021), so no encoding of an audit row can be written without naming
# them. It is listed apart from *platform_allow rather than folded into it so that exception stays visible: a second context
# dependency here would mean the package had stopped being platform code.
# catchup is the shared catch-up policy: when a host should be sent a state its latest command did not deliver, and the sweep that
# asks (issue #1071). Pure platform code with no context dependency at all, since a context maps its own command onto catchup.Latest
# rather than the package naming any context's types.
- package: "github.com/rawtrail/rawtrail/server/catchup"
shouldOnlyDependsOn:
internal: *platform_allow
- package: "github.com/rawtrail/rawtrail/server/auditoutbox"
shouldOnlyDependsOn:
internal:
- "github.com/rawtrail/rawtrail/server/identity/api"
- "github.com/rawtrail/rawtrail/server/sqlhelpers"
- "github.com/rawtrail/rawtrail/server/testdb"
# NOTE: server/testdb itself is a Go-leaf and imports no context, but its
# sub-package server/testdb/full is the cross-context fixture composer and
# imports each context's testkit. arch-go's package patterns are prefix-
# matched (github.com/rawtrail/rawtrail/server/testdb matches both leaf + full),
# so a single rule can't distinguish them. The leaf invariant is verified
# by inspection (server/testdb/open.go imports nothing in server/<X>/) and
# by the per-testkit rules below preventing transitive sneak-ins.
- package: "github.com/rawtrail/rawtrail/server/ui"
shouldOnlyDependsOn:
internal: *platform_allow
# ----- bootstrap/ access: enforced indirectly --------------------------------
#
# Plan §"Architecture lint" prescribes "Allow only `cmd/**` and
# `test/integration/**` to import any `bootstrap/` package."
# arch-go v1.7.0 exposes only "depends-on" rule families
# (shouldOnlyDependsOn / shouldNotDependsOn), not the inverse
# "shouldOnlyBeAccessedBy" the plan sketched against v2. The
# invariant is enforced indirectly by the importer-perspective
# rules already in this file:
#
# - Platform packages cannot import any context (incl. bootstraps).
# - <X>/api packages cannot import any other context's bootstrap
# (the api-purity allow-list excludes it).
# - <X>/internal/* packages cannot import another context's
# bootstrap (each context's allow-list lists only that context's
# own packages + cross-context **.<Y>.api / **.<Y>.testkit).
# - <X>/testkit may import its OWN context's bootstrap (per-testkit
# rule); other contexts' testkits are not in any internal
# allow-list, so cross-context testkit imports go through testkit
# too, not bootstrap.
#
# The remaining production-reachable bootstrap importers are
# cmd/rawtrail-{server,ingest} and each <X>/testkit. New packages
# outside those slots would have to be added explicitly; code review
# is the gate. If arch-go ever ships a "shouldOnlyBeAccessedBy" rule
# family in v2, this section becomes the single declarative
# source of truth instead of a property derived from many other
# rules.