Skip to content

Port remaining private features into public repo - #117

Open
phrocker wants to merge 6 commits into
mainfrom
port-private-features
Open

Port remaining private features into public repo#117
phrocker wants to merge 6 commits into
mainfrom
port-private-features

Conversation

@phrocker

Copy link
Copy Markdown
Contributor

Brings the public tree to parity with Sentrius-private. All changes are additive or supersede existing stubs; no public-only code was lost.

Features:

  • Deployment Environments: model, repository, service, REST controller and V49 migration for cross-cloud enclave tracking. Agent heartbeats now carry environmentName and refresh the environment last-contact timestamp.
  • Agent Forensics: AgentForensicsService plus four /forensics/* endpoints (trace, by-agent, by-user, timeline) on AuditApiController.
  • Behavioral pattern mining: BehavioralPatternAgent, AutomationAdoptionTracker, DriLifecycleService and four /dri/* endpoints on KnowledgeGraphController.
  • Topology view: TopologyViewController, topology.html and sidebar entry.
  • LLM analytics cost controls: ~15 new SystemOptions flags including an enableLLMAnalytics master kill switch and pattern-mining thresholds.
  • Trust scoring: AgentContext.getProvenanceScore() now scores the real provenance trail instead of returning a hardcoded 80.0.

Not ported:

  • ops-scripts/gcp/rendered-demo.yaml (rendered Helm output containing resolved Secret values); added a .gitignore rule so it cannot be committed here.
  • test-node-properties-update.sh (empty file).

Verified with mvn clean install on JDK 17: all 18 modules build, tests pass with 0 failures and 0 errors.

Claude-Session: https://claude.ai/code/session_01YLVutpMbp3msuhvxA46cU3

Brings the public tree to parity with Sentrius-private. All changes are
additive or supersede existing stubs; no public-only code was lost.

Features:
- Deployment Environments: model, repository, service, REST controller and
  V49 migration for cross-cloud enclave tracking. Agent heartbeats now carry
  environmentName and refresh the environment last-contact timestamp.
- Agent Forensics: AgentForensicsService plus four /forensics/* endpoints
  (trace, by-agent, by-user, timeline) on AuditApiController.
- Behavioral pattern mining: BehavioralPatternAgent, AutomationAdoptionTracker,
  DriLifecycleService and four /dri/* endpoints on KnowledgeGraphController.
- Topology view: TopologyViewController, topology.html and sidebar entry.
- LLM analytics cost controls: ~15 new SystemOptions flags including an
  enableLLMAnalytics master kill switch and pattern-mining thresholds.
- Trust scoring: AgentContext.getProvenanceScore() now scores the real
  provenance trail instead of returning a hardcoded 80.0.

Not ported:
- ops-scripts/gcp/rendered-demo.yaml (rendered Helm output containing resolved
  Secret values); added a .gitignore rule so it cannot be committed here.
- test-node-properties-update.sh (empty file).

Verified with mvn clean install on JDK 17: all 18 modules build, tests pass
with 0 failures and 0 errors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YLVutpMbp3msuhvxA46cU3

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved critical authorization, data-exposure, injection, and LLM-control issues remain.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Ports private deployment, forensics, behavioral analytics, topology, LLM controls, and provenance scoring into the public platform.

Changes:

  • Adds deployment-environment persistence, heartbeat tracking, topology APIs/UI, and migration.
  • Adds agent forensics, DRI lifecycle, behavioral mining, and automation adoption.
  • Adds analytics controls and provenance-based trust scoring.
File summaries
File Summary
dataplane/src/test/java/io/sentrius/sso/core/services/agents/AgentServiceTest.java Extends agent-service test setup.
dataplane/src/main/java/io/sentrius/sso/core/services/documents/KnowledgeGraphIngestionService.java Controls optional graph ingestion.
dataplane/src/main/java/io/sentrius/sso/core/services/documents/DriLifecycleService.java Provides DRI lifecycle queries and transfers.
dataplane/src/main/java/io/sentrius/sso/core/services/DeploymentEnvironmentService.java Manages environment and topology data.
dataplane/src/main/java/io/sentrius/sso/core/services/agents/AgentService.java Persists environment heartbeat data.
dataplane/src/main/java/io/sentrius/sso/core/services/agents/AgentForensicsService.java Assembles forensic traces and timelines.
dataplane/src/main/java/io/sentrius/sso/core/repository/DeploymentEnvironmentRepository.java Provides environment queries.
dataplane/src/main/java/io/sentrius/sso/core/repository/AgentMemoryRepository.java Provides agent-memory queries.
dataplane/src/main/java/io/sentrius/sso/core/repository/AgentExecutionAuditRepository.java Provides execution-audit queries.
dataplane/src/main/java/io/sentrius/sso/core/model/hostgroup/HostGroup.java Associates host groups with environments.
dataplane/src/main/java/io/sentrius/sso/core/model/DeploymentEnvironment.java Defines the deployment environment entity.
dataplane/src/main/java/io/sentrius/sso/core/model/agents/AgentExecutionAudit.java Stores execution environment metadata.
dataplane/src/main/java/io/sentrius/sso/core/model/AgentHeartbeat.java Persists heartbeat environment data.
dataplane/src/main/java/io/sentrius/sso/core/config/SystemOptions.java Adds analytics and mining controls.
core/src/test/java/io/sentrius/sso/core/trust/TrustScoreCalculatorTest.java Updates trust-score tests.
core/src/main/java/io/sentrius/sso/core/trust/AgentContext.java Calculates provenance-based trust context.
core/src/main/java/io/sentrius/sso/core/dto/AgentHeartbeatDTO.java Carries environment heartbeat data.
CLAUDE.md Updates repository guidance.
api/src/main/resources/templates/sso/topology.html Adds topology visualization.
api/src/main/resources/templates/fragments/sidebar.html Adds topology navigation.
api/src/main/resources/db/migration/V49__create_deployment_environments.sql Creates the environment schema.
api/src/main/java/io/sentrius/sso/controllers/view/TopologyViewController.java Routes the topology view.
api/src/main/java/io/sentrius/sso/controllers/api/documents/KnowledgeGraphController.java Exposes DRI endpoints.
api/src/main/java/io/sentrius/sso/controllers/api/DeploymentEnvironmentController.java Exposes environment and topology APIs.
api/src/main/java/io/sentrius/sso/controllers/api/AuditApiController.java Exposes forensic endpoints.
analytics/src/main/resources/application.properties Configures analytics agents.
analytics/src/main/java/io/sentrius/agent/analysis/agents/trust/TrustEvaluationService.java Builds provenance-aware trust calculations.
analytics/src/main/java/io/sentrius/agent/analysis/agents/sessions/SshSessionSummarizationAgent.java Applies SSH analytics controls.
analytics/src/main/java/io/sentrius/agent/analysis/agents/sessions/RdpSessionSummarizationAgent.java Applies RDP analytics controls.
analytics/src/main/java/io/sentrius/agent/analysis/agents/documents/DocumentRelationshipAnalyzer.java Applies document analytics controls.
analytics/src/main/java/io/sentrius/agent/analysis/agents/behavioral/BehavioralPatternAgent.java Mines behavioral patterns and runbooks.
analytics/src/main/java/io/sentrius/agent/analysis/agents/automation/AutomationAdoptionTracker.java Tracks automation adoption.
.gitignore Excludes rendered deployment output.
.gcp.env Updates deployment configuration metadata.
Review details

Suppressed comments (23)

analytics/src/main/java/io/sentrius/agent/analysis/agents/automation/AutomationAdoptionTracker.java:97

  • The scheduled job claims to inspect recent sessions, but this call returns every PROCESSED session and applies no time boundary. Historical commands are repeatedly reclassified as new adoption on every run, and the scan grows without bound. Restrict the repository query to a defined recent window (or persist a high-water mark).
            List<TerminalSessionMetadata> recentSessions = sessionMetadataService.getSessionsByState("PROCESSED");

analytics/src/main/java/io/sentrius/agent/analysis/agents/automation/AutomationAdoptionTracker.java:126

  • There is no existence check before creating the ADOPTED relationship. Because this method runs every eight hours over the same sessions, the same suggestion/session match is related again on every run; KnowledgeGraphService.createRelationship issues a plain RELATE, so the graph can accumulate duplicate edges and inflated adoption counts. Make the relationship creation idempotent or record processed matches.
                            createAdoptionRelationship(suggestion, session, overlapScore);

analytics/src/main/java/io/sentrius/agent/analysis/agents/automation/AutomationAdoptionTracker.java:194

  • The overlap loop lets one session command satisfy multiple script commands. For example, one rm -rf session command matches both rm and rm -rf, inflating the score and allowing a false adoption at the 50% threshold; consume each matched session command once or compare one-to-one.
            for (String sessionCmd : sessionCommands) {
                // Fuzzy match: session command starts with or contains the script command's core
                if (sessionCmd.contains(coreCmd) && fuzzyMatch(scriptCmd, sessionCmd)) {
                    matched++;
                    break;
                }
            }

analytics/src/main/java/io/sentrius/agent/analysis/agents/behavioral/BehavioralPatternAgent.java:343

  • getCommandsBySessionId loads the entire command table and filters it in memory. Calling it once per session inside this mining pass repeatedly reloads all commands, so a run scales with the number of sessions multiplied by the full command table and can consume substantial memory/DB time. Add a batched session-ID query or cache commands for the run.
                List<TerminalCommand> commands = commandService.getCommandsBySessionId(session.getId());

analytics/src/main/java/io/sentrius/agent/analysis/agents/behavioral/BehavioralPatternAgent.java:161

  • Each scheduled run loads every processed session, and the same class repeats that full-history query in workflow, incident, anomaly, and profile phases. As session history grows, one four-hour run repeatedly materializes the entire table and can exhaust memory or delay analytics; fetch a bounded window once or use paged aggregation queries.
        List<TerminalSessionMetadata> allSessions = sessionMetadataService.getSessionsByState("PROCESSED");

analytics/src/main/java/io/sentrius/agent/analysis/agents/behavioral/BehavioralPatternAgent.java:491

  • The configurable workflowSimilarityThreshold is never used here; every deployment uses a hard-coded 40% pair-frequency threshold. Changing the exposed option (whose default is 0.6) has no effect on workflow discovery.
        int threshold = Math.max(2, (int)(allSequences.size() * 0.4));

analytics/src/main/java/io/sentrius/agent/analysis/agents/behavioral/BehavioralPatternAgent.java:712

  • This only derives a deterministic runbook ID; it never checks whether the node exists. Every scheduled pass therefore regenerates the LLM runbook and calls CREATE for the same workflow, then creates another relationship attempt, causing repeated token spend and stale/duplicate graph data.
                // Check if a runbook already exists for this workflow
                String runbookId = "runbook:" + sanitizeId(workflowNode.getId().replace("workflow:", ""));

api/src/main/java/io/sentrius/sso/controllers/api/AuditApiController.java:468

  • limit is passed to Stream.limit() without validation. A request such as limit=-1 raises IllegalArgumentException and becomes a server error, while an arbitrarily large value can produce an unnecessarily large response. Reject non-positive/over-limit values at the controller boundary.
        @RequestParam(defaultValue = "50") int limit,

api/src/main/java/io/sentrius/sso/controllers/api/AuditApiController.java:496

  • The existence check passes maxCommunications=0, but getForensicsTrace uses that value as the page size in PageRequest.of when the execution has no communication rows. A valid audit with an empty timeline can therefore throw instead of returning an empty list; use a positive internal page size or expose a separate audit-exists lookup.
            var auditOpt = agentForensicsService.getForensicsTrace(executionId, userId, false, 0, 0);

api/src/main/java/io/sentrius/sso/controllers/api/DeploymentEnvironmentController.java:105

  • This read endpoint has no @LimitAccess check, while getTopologyData() returns every environment, host group, host, and agent heartbeat. Ordinary authenticated users can therefore enumerate the full infrastructure topology instead of receiving only their assigned hosts as HostApiController does. Require the appropriate management/SSH privilege or make the service build a user-scoped graph.
    @GetMapping("/topology")
    public ResponseEntity<TopologyGraph> getTopology() {
        try {
            TopologyGraph graph = environmentService.getTopologyData();

api/src/main/java/io/sentrius/sso/controllers/view/TopologyViewController.java:30

  • This new infrastructure page has no @LimitAccess check, while neighboring topology/infrastructure views are permission-gated. A user can open the page (and it then calls the topology API) without the system-view permission; protect the view as well as the API endpoint.
    @GetMapping
    public String topology(Model model) {

dataplane/src/main/java/io/sentrius/sso/core/config/SystemOptions.java:245

  • Both graph-ingestion switches default to true, so upgrading enables persistent storage of terminal sessions and agent executions without an explicit opt-in. This can materially increase graph storage/cost and expand the sensitive-data exposure described by the markings; keep ingestion opt-in or require an explicit deployment configuration for the new behavior.
    public Boolean knowledgeGraphSessionsEnabled = true;

    @Updatable(description = "Enable storing agent execution data in the knowledge graph", group = "Knowledge Graph")
    @Builder.Default
    public Boolean knowledgeGraphAgentExecutionsEnabled = true;

dataplane/src/main/java/io/sentrius/sso/core/model/hostgroup/HostGroup.java:116

  • The new persisted HostGroup.environmentName is not present in HostGroupDTO and is not copied by HostGroup.toDTO(). Existing host-group create/update APIs therefore cannot set or round-trip this value, leaving topology environment→host-group edges null for all API-managed groups; add the field to the DTO and mapping/input path.
  @Column(name = "environment_name")
  private String environmentName;

dataplane/src/main/java/io/sentrius/sso/core/services/agents/AgentForensicsService.java:132

  • The requestingUserId parameter is also ignored here, allowing a caller to enumerate another user's execution summaries by supplying that user's username. Do not treat the requested username as authorization; verify the requester is allowed to view that user's audits.
    public List<ForensicsSummary> getForensicsByUser(String username, String requestingUserId, int limit) {
        List<AgentExecutionAudit> audits = auditRepository.findByExecutedByOrderByStartTimeDesc(username);
        return audits.stream()
                .limit(limit)
                .map(this::toForensicsSummary)

dataplane/src/main/java/io/sentrius/sso/core/services/agents/AgentForensicsService.java:155

  • These forensic fields are populated from the audit entity, but AgentExecutionAuditService.createAudit() never sets environmentName and there are no other setters in the codebase. Consequently the new environment field in forensic metadata/summaries remains null for every audit. Wire the environment through audit creation or derive it from the agent heartbeat.
        m.environmentName = audit.getEnvironmentName();

dataplane/src/main/java/io/sentrius/sso/core/services/agents/AgentForensicsService.java:194

  • The timeline's empty-result existence check calls getForensicsTrace(..., maxCommunications=0, ...). If the UUID lookup has no rows, the fallback below constructs PageRequest.of(0, 0, ...), which throws before the valid audit can be returned; normalize the repository page size to at least one while still returning zero communication entries.
                    PageRequest.of(0, maxCommunications, Sort.by(Sort.Direction.ASC, "createdAt")));

dataplane/src/main/java/io/sentrius/sso/core/services/agents/AgentForensicsService.java:121

  • This query has no pagination or server-side cap, so a wide time range materializes every execution audit for an agent in memory and serializes the full list. The endpoint accepts arbitrary ranges; use a bounded/pageable repository query to prevent an unbounded forensic response.
        List<AgentExecutionAudit> audits = auditRepository.findByAgentIdAndStartTimeBetween(agentId, startTime, endTime);

dataplane/src/main/java/io/sentrius/sso/core/services/agents/AgentForensicsService.java:131

  • The endpoint's limit is applied only after findByExecutedByOrderByStartTimeDesc has materialized every matching audit row. As forensic history grows, a caller can trigger a full user-history load despite requesting a small limit; use a Pageable repository query and enforce a maximum in the database.
        List<AgentExecutionAudit> audits = auditRepository.findByExecutedByOrderByStartTimeDesc(username);
        return audits.stream()
                .limit(limit)

dataplane/src/main/java/io/sentrius/sso/core/services/agents/AgentService.java:101

  • Because the heartbeat client can omit the new environmentName field, this unconditional assignment clears an existing environment association on every such heartbeat. Preserve the current value when the incoming name is null or blank, and only update it for a valid name; otherwise agents disappear from their environment in topology after their next heartbeat.
    dataplane/src/main/java/io/sentrius/sso/core/services/documents/DriLifecycleService.java:63
  • This opts the DRI response into relationship expansion, but KnowledgeGraphService filters the initial nodes and then appends connected nodes without reapplying ABAC. A relationship from an allowed node can therefore pull a marked connected node into the response. Filter connected nodes for the authenticated requester before returning this subgraph.
    dataplane/src/main/java/io/sentrius/sso/core/services/documents/DriLifecycleService.java:63
  • This requests relationships, but KnowledgeGraphService's relationship fetcher only scans the six document tables (DISCUSSES, CONTAINS_PROCEDURE, RELATED_TO, REFERENCES, SUPERSEDES, DEPENDS_ON). The behavioral graph uses OWNS, CONNECTED_TO, PERFORMS_ROUTINELY, and similar edges, so DRI responses contain nodes but omit the relationships needed to render ownership and workflow links.
    dataplane/src/main/java/io/sentrius/sso/core/services/documents/DriLifecycleService.java:235
  • The SEARCH request is intended to find runbook nodes, but KnowledgeGraphService.buildSearchQuery always selects from the document table; generated runbooks are stored with runbook:... record IDs. As a result this endpoint will normally return no runbooks even though matching runbook nodes exist; query the runbook table or add a cross-node search path.
    dataplane/src/test/java/io/sentrius/sso/core/services/agents/AgentServiceTest.java:56
  • The new mock is unused: testRecordHeartbeat still builds a DTO without environmentName and only verifies the existing heartbeat fields. It does not cover persistence of the environment association, touching the environment, or the null/blank compatibility case, so the new heartbeat behavior can regress without failing tests.
  • Files reviewed: 33/34 changed files
  • Comments generated: 24
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.


// Phase 4: Generate runbooks from workflows (LLM-powered)
int runbooksGenerated = 0;
if (systemOptions.autoGenerateRunbooks != null && systemOptions.autoGenerateRunbooks) {
Comment on lines +781 to +783
String stepsText = String.join("\n", steps.stream()
.map(s -> "- " + s)
.collect(Collectors.toList()));
* knowledge graph context, and a unified timeline.
*/
@GetMapping("/agents/audit/forensics/trace")
@LimitAccess(applicationAccess = {ApplicationAccessEnum.CAN_LOG_IN})
Comment on lines +33 to +34
@GetMapping
public ResponseEntity<List<DeploymentEnvironment>> listAll() {
</div>
</div>

<script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
Comment on lines +132 to +134
public int markStaleEnvironments(Duration threshold) {
Instant cutoff = Instant.now().minus(threshold);
List<DeploymentEnvironment> stale = repository.findByLastContactAtBefore(cutoff);
/**
* Build a topology graph combining environments, host groups, hosts, and agents.
*/
public TopologyGraph getTopologyData() {
.limit(50)
.build();

KnowledgeGraphQueryResponse response = knowledgeGraphService.executeQuery(queryRequest, "system", true);
.collect(Collectors.toList());

// Transfer all runbooks
pkg.runbooks = previousLifecycle.runbooks;
.limit(50)
.build();

var response = knowledgeGraphService.executeQuery(queryRequest, "system");
phrocker and others added 5 commits September 11, 2026 12:15
…xecutions

Two independent correctness fixes in access enforcement and audit retention.

ZTAT use counting was a read-check-increment-save sequence guarded by neither
@Version nor a row lock. Under READ_COMMITTED two concurrent callers could both
observe the same pre-increment value, both pass the maxJitUses check, and both
be granted access while the counter advanced only once. The limit check and the
increment now happen in a single conditional UPDATE via claimUse(), and callers
treat a return of 0 as "limit exceeded". The stale-entity save() calls are gone;
leaving them would have clobbered the atomic update. Policy semantics are
unchanged - the bound is still uses < maxJitUses.

The ops branch of incrementAccessTokenUses also advanced the counter without
writing an OpsUse row, so the counter and the use log diverged by construction
and nothing reconciled them. Both branches now record the use only after the
claim succeeds.

Audit consolidation grouped rows by agentType + agentId + status + executedBy,
with no executionId and no time component, then deleted every row in the group
but the most recent. Two distinct runs by the same user of the same agent with
the same outcome were merged, permanently losing one execution's executionId,
summary, podLogs, resourceLinks and exitCode. Forensics traces by executionId,
so that loss was not recoverable elsewhere, and the job runs on a schedule from
AgentExecutionSummarizationAgent. The key is now anchored on executionId, and a
row without one keys on its own id so it consolidates with nothing.

Tests were confirmed to fail against the previous behaviour before being kept.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YLVutpMbp3msuhvxA46cU3
…place

No deployment ever ran SurrealDB, so the knowledge graph has only ever exhibited
its degraded path. The backend is removed; the features built on it are not.

BehavioralPatternAgent, AutomationAdoptionTracker, DriLifecycleService,
AgentForensicsService and DocumentService all read and write the graph. Deleting
the store outright would have taken roughly 1,700 lines of recently ported code
with it, so the vendor implementation is replaced by a seam instead:

- KnowledgeGraphPort - the 11 methods consumers actually call, storage-neutral.
- UnavailableKnowledgeGraph - inert fallback reproducing exactly the degraded
  behaviour the SurrealDB implementation showed with its provider disabled:
  writes dropped, queries empty, statistics reporting a disconnected store.
- KnowledgeGraphConfiguration - supplies the fallback only when nothing else
  provides the port. The condition sits on a @bean method rather than the class,
  because @ConditionalOnMissingBean is only evaluated reliably against already
  registered definitions and not between two scanned components.

Removed: KnowledgeGraphService (2,391 lines), SurrealDBConfig and its test, the
com.surrealdb dependency from api and dataplane, five Helm templates and their
values block, the SystemOptions configuration, configmap properties, the
SURREALDB_PASSWORD plumbing, and the secret generation and deploy script wiring.
No surreal reference remains in the tree and helm lint passes.

Dropping the 212 MB driver takes the api boot jar from roughly 352 MB to 140 MB.

Note: no working Spring context-load test exists for the api module - one is
@disabled and one has its @test commented out, both predating this change - so
the bean graph is verified in isolation rather than through a full context boot.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YLVutpMbp3msuhvxA46cU3
claimUse is annotated clearAutomatically = true, so the persistence context is
cleared as soon as it runs and every managed entity becomes detached. Both
increment paths then resolved a lazy @manytoone for the use record -
request.getUser() and request.getApprover() - which would have thrown
LazyInitializationException whenever the proxy had not already been initialized.

The use record is now built before the claim and saved after it, so every lazy
association is resolved while the context is still live, and the record is still
only persisted once the claim has succeeded. Added an ordering test so the two
cannot be reversed again.

Found while reviewing 4339dd0; clearAutomatically is retained deliberately, since
it is what prevents a stale in-memory counter being read after the claim.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YLVutpMbp3msuhvxA46cU3
Adversarial review of a968eb6 found that replacing an optionally absent bean
with an always present inert one silently broke every consumer's guard.

com.surrealdb was <optional>true</optional> in dataplane, so it never reached
the classpath of analytics, ssh-proxy or rdp-proxy. KnowledgeGraphService was
@ConditionalOnClass, so in those applications the bean genuinely did not exist
and every "knowledgeGraphService == null" guard short-circuited. Supplying a
fallback made all of those guards dead, so the work they were guarding began to
run and its results were discarded:

- BehavioralPatternAgent.analyzePatterns is @scheduled every four hours and
  scans the session tables, mines command sequences and generates runbooks via
  LLM; it counted routines, workflows and incidents as discovered while every
  write was dropped.
- KnowledgeGraphIngestionService gated only on the bean existing, so
  AuditService.closeSession loaded the full terminal log set and built a node
  graph synchronously on every SSH and RDP session teardown, into a no-op sink.
- AutomationAdoptionTracker ran the same way every eight hours.
- KnowledgeGraphController returned 500 rather than 503 for an unconfigured
  install, and DriLifecycleService returned an empty DRI view as a fact rather
  than reporting the graph as unavailable.

Every consumer now asks isAvailable(). That is also the check the eventual
adapter needs, since a bean will exist while its backing store is unreachable.

Also from the review: the fallback's statistics regained the enabled and
database keys that knowledge_graph.html renders, and selection moved to a real
@autoConfiguration outside any component-scanned package. On a plain
@configuration, @ConditionalOnMissingBean is evaluated in registration order, so
an adapter contributed by a later-processed configuration would have left two
beans and failed startup with NoUniqueBeanDefinitionException. The test now
drives it through AutoConfigurations so the ordering guarantee is actually
exercised; the previous version registered the stub first and could not fail.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YLVutpMbp3msuhvxA46cU3
… cert ordering

Found while standing the full stack up on a clean cluster. Each of these only
shows on a cluster that is not already configured, which is why they survived.

build_image reported success for failed builds. The exit status was read after
an intervening if statement, so $? held that statement's result rather than
docker build's - and a false condition yields 0. A failed build printed
"Built locally" and the deploy continued to ImagePullBackOff. The status is now
captured immediately and checked before the Minikube sync.

ssh-agent never built at all: the jar copy read \/target/ssh-agent-*.jar, which
resolves to an absolute /target path rather than the module directory, so the
cp failed and the Docker COPY then failed on the missing jar. Masked entirely by
the bug above. Two assignments in the same block were written $VAR=value, which
expands the variable and tries to run the result as a command instead of
assigning; both corrected.

prompt-advisor pointed at Dockerfile-prompt-advisor in the repository root,
which does not exist - the Dockerfile is docker/prompt-advisor/Dockerfile. It
copies prompt-advisor/... from the root, so the context stays the root and only
the -f path changes. prepare_docker_context cannot serve it, since that resolves
dev-certs relative to the context's parent, which is outside the repository for
a root context, so the certs are staged inline instead.

deploy-helm.sh created the sentrius-dev-ca secret in the cert-manager namespace
before installing cert-manager, so on a cluster without it the secret always
failed to create and the Issuer had no CA to sign from. The namespace is now
ensured first; helm --create-namespace later adopts it.

Added a root .dockerignore so the prompt-advisor root-context build stops
shipping every module's target directory as build context.

Verified on a clean minikube cluster: all 17 pods in dev plus the launcher in
dev-agents reach Running, wildcard-cert-dev is issued, and the api serves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YLVutpMbp3msuhvxA46cU3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants