The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.
Follows the import procedure in README.md.
source/ the originating repository verbatim — 78 documents, 28 SVGs,
8 manifests, plus .source-revision recording the commit
final/ the SSOT
document.md 729 lines written from the 29 experiment documents, not
concatenated: what was predicted, what was measured, and
where the measurement itself was wrong
evidence/raw 125 outputs, flattened to <experiment>__<file> because
the originals collided (01-baseline.txt appeared three
times) and the audit only globs the top level
evidence/meta one per raw file; command and exitCode are null and the
README says why rather than inventing them
evidence/browser 22 captures
assets/ three diagrams through techviz
.techviz/ their VizSpecs
A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.
Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.
verify-pipeline.py passes. audit-records.py reports no issues.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
118 lines
7.3 KiB
Plaintext
118 lines
7.3 KiB
Plaintext
Command: printf '%s\n' '--- support config annotations ---'; grep -n -E '@Configuration|@Bean|@Conditional' src/adapter/outbound/support/src/main/java/dev/caskeleton/adapter/outbound/support/OutboundSupportConfig.java; printf '%s\n' '--- app component scan/exclusions ---'; sed -n '35,125p' src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java; printf '%s\n' '--- registry support membership/consumers ---'; python3 - <<'PY'
|
|
import json
|
|
mods=json.load(open('src/config/architecture/modules.json'))['modules']
|
|
for m in mods:
|
|
if m['id']=='adapter-outbound-support' or 'adapter-outbound-support' in m.get('allowed_dependencies',[]):
|
|
print(m['id'],m['gradle_path'],'runtime=',m.get('runtime_memberships'),'allows=',m.get('allowed_dependencies'))
|
|
PY
|
|
printf '%s\n' '--- explicit support config references ---'; git grep -n -w 'OutboundSupportConfig' -- 'src/**' ':!src/adapter/outbound/support/**'; echo "git-grep-exit=$?"
|
|
Working directory: /shared/codebase/clean-architecture-backend-template
|
|
Executed at: 2026-08-29T07:19:26Z
|
|
Source revision: a24ece9cf797f7ea647e33bf846b115208ed1ba5
|
|
Observation boundary: Shows support configuration annotations, composition-root component-scan ownership, optional-adapter config references, and registry runtime membership; it does not boot the application.
|
|
--- stdout/stderr ---
|
|
--- support config annotations ---
|
|
10: * {@code @ConditionalOnMissingBean} lets a fork override it.
|
|
12:@Configuration
|
|
15: @Bean
|
|
16: @ConditionalOnMissingBean
|
|
--- app component scan/exclusions ---
|
|
* component scanner discover each child configuration on its own and assemble the capability
|
|
* whatever that switch said. Registering them as auto-configuration instead makes "off" a
|
|
* structural fact rather than a condition every future bean has to remember to repeat.
|
|
*/
|
|
@SpringBootConfiguration
|
|
@EnableAutoConfiguration
|
|
@ComponentScan(
|
|
basePackages = {
|
|
"dev.caskeleton.bootstrap",
|
|
"dev.caskeleton.adapter",
|
|
"dev.caskeleton.application",
|
|
"dev.caskeleton.domain",
|
|
"dev.caskeleton.shared"
|
|
},
|
|
excludeFilters = {
|
|
@ComponentScan.Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
|
|
@ComponentScan.Filter(
|
|
type = FilterType.CUSTOM,
|
|
classes = AutoConfigurationExcludeFilter.class),
|
|
@ComponentScan.Filter(
|
|
type = FilterType.REGEX,
|
|
pattern = CaSkeletonApplication.AUTO_CONFIGURED_PACKAGES)
|
|
})
|
|
@ConfigurationPropertiesScan(
|
|
basePackages = {
|
|
"dev.caskeleton.bootstrap.async",
|
|
"dev.caskeleton.bootstrap.autoconfigure.fileserver",
|
|
"dev.caskeleton.bootstrap.autoconfigure.httpclient",
|
|
"dev.caskeleton.bootstrap.concurrency",
|
|
"dev.caskeleton.bootstrap.idempotency",
|
|
"dev.caskeleton.bootstrap.outbox",
|
|
"dev.caskeleton.bootstrap.redis",
|
|
"dev.caskeleton.bootstrap.runtime",
|
|
"dev.caskeleton.bootstrap.security",
|
|
"dev.caskeleton.bootstrap.settings",
|
|
"dev.caskeleton.bootstrap.tracing",
|
|
"dev.caskeleton.adapter.inbound.grpc",
|
|
"dev.caskeleton.adapter.inbound.web",
|
|
"dev.caskeleton.adapter.inbound.websocket",
|
|
"dev.caskeleton.adapter.outbound.cache.redis",
|
|
"dev.caskeleton.adapter.outbound.fileserver",
|
|
"dev.caskeleton.adapter.outbound.objectstorage",
|
|
"dev.caskeleton.application",
|
|
"dev.caskeleton.domain",
|
|
"dev.caskeleton.shared"
|
|
})
|
|
public class CaSkeletonApplication {
|
|
|
|
/**
|
|
* Packages owned by an auto-configuration entry rather than by the component scan.
|
|
*
|
|
* <p>A package prefix rather than a class list: a new configuration added to an optional
|
|
* capability must not become active merely because nobody remembered to exclude it.
|
|
*
|
|
* <p>The Fileserver admin package is here for a different reason: those routes belong to the
|
|
* management context, and a component scan that also found them would publish the management
|
|
* plane on the public connector — the exposure the separate context exists to remove.
|
|
*
|
|
* <p>The web platform's error, budget and operation packages are here for a third reason. Their
|
|
* advices and controllers need beans that only exist when the corresponding platform
|
|
* auto-configuration is active, and a component scan finds them regardless — so an all-off or
|
|
* partially configured deployment failed to start on an unsatisfied dependency rather than simply
|
|
* not installing the control. Ownership by auto-configuration is what ties a control's presence
|
|
* to its dependency's.
|
|
*/
|
|
static final String AUTO_CONFIGURED_PACKAGES =
|
|
"dev\\.caskeleton\\.bootstrap\\.autoconfigure\\..*"
|
|
+ "|dev\\.caskeleton\\.bootstrap\\.notification\\..*"
|
|
+ "|dev\\.caskeleton\\.adapter\\.inbound\\.web\\.fileserver\\.admin\\..*"
|
|
+ "|dev\\.caskeleton\\.adapter\\.outbound\\.mongo\\..*"
|
|
+ "|dev\\.caskeleton\\.adapter\\.outbound\\.messaging\\..*"
|
|
+ "|dev\\.caskeleton\\.adapter\\.outbound\\.notification\\..*"
|
|
+ "|dev\\.caskeleton\\.adapter\\.outbound\\.persistence\\..*"
|
|
+ "|dev\\.caskeleton\\.adapter\\.inbound\\.graphql\\..*"
|
|
+ "|dev\\.caskeleton\\.adapter\\.inbound\\.web\\.mvc\\.error\\..*"
|
|
+ "|dev\\.caskeleton\\.adapter\\.inbound\\.web\\.mvc\\.budget\\..*"
|
|
+ "|dev\\.caskeleton\\.adapter\\.inbound\\.web\\.mvc\\.operation\\..*"
|
|
+ "|dev\\.caskeleton\\.adapter\\.inbound\\.web\\.webflux\\.error\\..*"
|
|
+ "|dev\\.caskeleton\\.adapter\\.inbound\\.web\\.webflux\\.operation\\..*"
|
|
+ "|dev\\.caskeleton\\.messaging\\..*";
|
|
|
|
public static void main(String[] args) {
|
|
SpringApplication.run(CaSkeletonApplication.class, args);
|
|
}
|
|
}
|
|
--- registry support membership/consumers ---
|
|
adapter-outbound-support :adapter:outbound:support runtime= ['app-bootstrap'] allows= ['domain-core', 'application-core', 'shared-contract']
|
|
adapter-outbound-cache-redis :adapter:outbound:cache-redis runtime= ['app-bootstrap'] allows= ['domain-core', 'application-core', 'shared-contract', 'adapter-outbound-support']
|
|
adapter-outbound-httpclient :adapter:outbound:httpclient runtime= ['app-bootstrap'] allows= ['domain-core', 'application-core', 'shared-contract', 'adapter-outbound-support']
|
|
adapter-outbound-messaging :adapter:outbound:messaging runtime= ['app-bootstrap'] allows= ['domain-core', 'application-core', 'shared-contract', 'adapter-outbound-support']
|
|
adapter-outbound-notification :adapter:outbound:notification runtime= ['app-bootstrap'] allows= ['domain-core', 'application-core', 'shared-contract', 'adapter-outbound-support']
|
|
app-bootstrap :app-bootstrap runtime= ['app-bootstrap'] allows= ['domain-core', 'application-core', 'adapter-outbound-persistence-jpa', 'adapter-outbound-support', 'adapter-outbound-messaging', 'adapter-outbound-cache-redis', 'adapter-outbound-notification', 'adapter-outbound-fileserver', 'adapter-outbound-httpclient', 'adapter-outbound-identifier', 'adapter-inbound-web', 'shared-contract', 'adapter-outbound-persistence-mongo', 'adapter-inbound-graphql', 'messaging-spring-boot-starter']
|
|
--- explicit support config references ---
|
|
src/app-bootstrap/src/test/java/dev/caskeleton/adapter/outbound/OptionalAdapterBeanGatingTest.java:27:import dev.caskeleton.adapter.outbound.support.OutboundSupportConfig;
|
|
src/app-bootstrap/src/test/java/dev/caskeleton/adapter/outbound/OptionalAdapterBeanGatingTest.java:66: OutboundSupportConfig.class,
|
|
git-grep-exit=0
|
|
|
|
Exit code: 0
|