Port remaining private features into public repo - #117
Conversation
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
There was a problem hiding this comment.
🟡 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
PROCESSEDsession 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
ADOPTEDrelationship. Because this method runs every eight hours over the same sessions, the same suggestion/session match is related again on every run;KnowledgeGraphService.createRelationshipissues a plainRELATE, 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 -rfsession command matches bothrmandrm -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
getCommandsBySessionIdloads 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
workflowSimilarityThresholdis 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
CREATEfor 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
limitis passed toStream.limit()without validation. A request such aslimit=-1raisesIllegalArgumentExceptionand 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
@LimitAccesscheck, whilegetTopologyData()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 asHostApiControllerdoes. 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
@LimitAccesscheck, 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.environmentNameis not present inHostGroupDTOand is not copied byHostGroup.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
requestingUserIdparameter is also ignored here, allowing a caller to enumerate another user's execution summaries by supplying that user'susername. Do not treat the requestedusernameas 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 setsenvironmentNameand 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 constructsPageRequest.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
KnowledgeGraphServicefilters 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) { |
| 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}) |
| @GetMapping | ||
| public ResponseEntity<List<DeploymentEnvironment>> listAll() { |
| </div> | ||
| </div> | ||
|
|
||
| <script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script> |
| 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"); |
…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
Brings the public tree to parity with Sentrius-private. All changes are additive or supersede existing stubs; no public-only code was lost.
Features:
Not ported:
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