Skip to content

Commit da9b9ba

Browse files
fix(server): scope observations to their workspace
1 parent 46c6831 commit da9b9ba

46 files changed

Lines changed: 457 additions & 139 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@ls1intum/hephaestus": patch
3+
---
4+
5+
Keeps historical practice observations attributable to the exact criteria that produced them and enforces workspace isolation directly on every observation.

docs/contributor/erd/schema.mmd

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -780,9 +780,10 @@ erDiagram
780780
TEXT evidence_rationale
781781
TIMESTAMPTZ observed_at "NOT NULL"
782782
VARCHAR(64) recurrence_key
783-
BIGINT practice_revision_id FK
783+
BIGINT practice_revision_id FK "NOT NULL"
784784
VARCHAR(8) assessment
785785
VARCHAR(16) origin "NOT NULL"
786+
BIGINT workspace_id FK "NOT NULL"
786787
}
787788

788789
Organization {
@@ -865,7 +866,7 @@ erDiagram
865866
}
866867

867868
Practice {
868-
BIGINT id PK
869+
BIGINT id PK,UK
869870
BIGINT workspace_id FK,UK "NOT NULL"
870871
VARCHAR(64) slug UK "NOT NULL"
871872
VARCHAR(128) name "NOT NULL"
@@ -1572,6 +1573,7 @@ erDiagram
15721573
User ||--o{ Milestone : created_by
15731574
Practice ||--o{ Observation : has
15741575
PracticeRevision ||--o{ Observation : has
1576+
Workspace ||--o{ Observation : has
15751577
AgentJob ||--o{ Observation : has
15761578
User ||--o{ Observation : has
15771579
IdentityProvider ||--o{ Organization : references

server/src/main/java/de/tum/cit/aet/hephaestus/agent/handler/FeedbackLedgerRecorder.java

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,10 @@ private void record(
189189
if (!summaryDelivered && !inlineDelivered) {
190190
return;
191191
}
192-
List<Observation> observations = observationRepository.findByAgentJobId(job.getId());
192+
List<Observation> observations = observationRepository.findByAgentJobId(
193+
job.getId(),
194+
job.getWorkspace().getId()
195+
);
193196
if (observations.isEmpty()) {
194197
return;
195198
}
@@ -437,7 +440,10 @@ private void recordSuppressedUnitInCurrentTransaction(
437440
if (feedbackRepository.existsByAgentJobIdAndPosition(job.getId(), GATE_SUPPRESSED_UNIT_ORDINAL)) {
438441
return; // already recorded (job retry)
439442
}
440-
List<Observation> observations = observationRepository.findByAgentJobId(job.getId());
443+
List<Observation> observations = observationRepository.findByAgentJobId(
444+
job.getId(),
445+
job.getWorkspace().getId()
446+
);
441447
if (observations.isEmpty()) {
442448
return;
443449
}
@@ -457,7 +463,10 @@ public void recordSuppressedRemainder(
457463
if (feedbackRepository.existsByAgentJobIdAndPosition(job.getId(), GATE_SUPPRESSED_UNIT_ORDINAL)) {
458464
return;
459465
}
460-
List<Observation> observations = observationRepository.findByAgentJobId(job.getId());
466+
List<Observation> observations = observationRepository.findByAgentJobId(
467+
job.getId(),
468+
job.getWorkspace().getId()
469+
);
461470
if (observations.isEmpty()) {
462471
return;
463472
}
@@ -526,7 +535,10 @@ public void recordRecoveredSummary(AgentJob job, String externalRef, String body
526535
if (feedbackRepository.existsByAgentJobIdAndPosition(job.getId(), IN_CONTEXT_UNIT_ORDINAL)) {
527536
return;
528537
}
529-
List<Observation> observations = observationRepository.findByAgentJobId(job.getId());
538+
List<Observation> observations = observationRepository.findByAgentJobId(
539+
job.getId(),
540+
job.getWorkspace().getId()
541+
);
530542
if (observations.isEmpty()) {
531543
return;
532544
}
@@ -575,7 +587,10 @@ public void recordRecoveredSummary(AgentJob job, String externalRef, String body
575587

576588
@Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
577589
public Optional<String> priorLiveIssueSummaryRef(AgentJob job) {
578-
List<Observation> observations = observationRepository.findByAgentJobId(job.getId());
590+
List<Observation> observations = observationRepository.findByAgentJobId(
591+
job.getId(),
592+
job.getWorkspace().getId()
593+
);
579594
if (observations.isEmpty()) {
580595
return Optional.empty();
581596
}
@@ -625,7 +640,7 @@ public void recordProposal(AgentJob job, @Nullable DeliveryContent delivery, Lis
625640
if (body.isBlank()) return;
626641
if (feedbackRepository.existsByAgentJobIdAndPosition(job.getId(), position)) return;
627642
Map<String, Observation> stored = observationRepository
628-
.findByAgentJobId(job.getId())
643+
.findByAgentJobId(job.getId(), job.getWorkspace().getId())
629644
.stream()
630645
.filter(observation -> observation.getOccurrenceKey() != null)
631646
.collect(
@@ -755,7 +770,10 @@ public void recordUndelivered(AgentJob job, @Nullable DeliveryContent delivery)
755770
if (feedbackRepository.existsByAgentJobIdAndPosition(job.getId(), UNDELIVERED_UNIT_ORDINAL)) {
756771
return; // already recorded (job retry)
757772
}
758-
List<Observation> observations = observationRepository.findByAgentJobId(job.getId());
773+
List<Observation> observations = observationRepository.findByAgentJobId(
774+
job.getId(),
775+
job.getWorkspace().getId()
776+
);
759777
if (observations.isEmpty()) {
760778
return;
761779
}

server/src/main/java/de/tum/cit/aet/hephaestus/agent/handler/InContextDeliveryGate.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,10 @@ public List<ValidatedObservation> awaitingApproval(AgentJob job, List<ValidatedO
163163

164164
private void recordWithheld(AgentJob job, List<ValidatedObservation> withheld, FeedbackSuppressionReason reason) {
165165
Map<String, Observation> byOccurrence = new HashMap<>();
166-
for (Observation observation : observationRepository.findByAgentJobId(job.getId())) {
166+
for (Observation observation : observationRepository.findByAgentJobId(
167+
job.getId(),
168+
job.getWorkspace().getId()
169+
)) {
167170
byOccurrence.put(observation.getOccurrenceKey(), observation);
168171
}
169172
int index = 0;

server/src/main/java/de/tum/cit/aet/hephaestus/agent/handler/IssueReviewHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ private String buildPrompt(int issueNumber, String repoName, AgentJob job) {
254254
public void deliver(AgentJob job) {
255255
ObservationAdmissionService.requireMatchingCompositionDigest(job);
256256
List<PracticeDetectionResultParser.ValidatedObservation> observations = observationRepository
257-
.findByAgentJobId(job.getId())
257+
.findByAgentJobId(job.getId(), job.getWorkspace().getId())
258258
.stream()
259259
.map(this::validated)
260260
.toList();

server/src/main/java/de/tum/cit/aet/hephaestus/agent/handler/ObservationAdmissionService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public ObjectNode admit(UUID jobId, JsonNode submitted) {
6262
String existing = currentMetadata == null ? "" : currentMetadata.path(DIGEST_METADATA_KEY).asString();
6363
if (!existing.isBlank()) {
6464
if (!existing.equals(digest)) throw new AdmissionConflictException();
65-
return response(job, existing, observations.findByAgentJobId(jobId));
65+
return response(job, existing, observations.findByAgentJobId(jobId, job.getWorkspace().getId()));
6666
}
6767
switch (job.getJobType()) {
6868
case PULL_REQUEST_REVIEW -> pullRequests.admitObservations(job, submitted);
@@ -74,7 +74,7 @@ public ObjectNode admit(UUID jobId, JsonNode submitted) {
7474
metadata.put(DIGEST_METADATA_KEY, digest);
7575
job.setMetadata(metadata);
7676
jobs.save(job);
77-
return response(job, digest, observations.findByAgentJobId(jobId));
77+
return response(job, digest, observations.findByAgentJobId(jobId, job.getWorkspace().getId()));
7878
}
7979

8080
private byte[] serializedPayload(JsonNode submitted) {

server/src/main/java/de/tum/cit/aet/hephaestus/agent/handler/PracticeDetectionDeliveryService.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ public DeliveryResult deliver(AgentJob job, List<ValidatedObservation> validObse
275275
UUID.randomUUID(),
276276
occurrenceKey,
277277
job.getId(),
278+
job.getWorkspace().getId(),
278279
practice.getId(),
279280
practiceRevisionId,
280281
artifactKind.value(),

server/src/main/java/de/tum/cit/aet/hephaestus/agent/handler/PullRequestReviewHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ public void deliver(AgentJob job) {
319319

320320
private void deliverAdmitted(AgentJob job) {
321321
List<PracticeDetectionResultParser.ValidatedObservation> scopedObservations = observationRepository
322-
.findByAgentJobId(job.getId())
322+
.findByAgentJobId(job.getId(), job.getWorkspace().getId())
323323
.stream()
324324
.map(this::validated)
325325
.toList();

server/src/main/java/de/tum/cit/aet/hephaestus/agent/handler/ReactionSuppressionFilter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public ReactionDecision evaluate(AgentJob job, List<ValidatedObservation> scoped
7272
if (!reviewProperties.reactionSuppression()) {
7373
return new ReactionDecision(scopedObservations, 0);
7474
}
75-
List<Observation> persisted = observationRepository.findByAgentJobId(job.getId());
75+
List<Observation> persisted = observationRepository.findByAgentJobId(job.getId(), job.getWorkspace().getId());
7676
if (persisted.isEmpty()) {
7777
return new ReactionDecision(scopedObservations, 0);
7878
}

server/src/main/java/de/tum/cit/aet/hephaestus/agent/handler/conversation/ConversationalDeliveryListener.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ private int route(UUID agentJobId, UUID outputJobId, Long workspaceId) {
114114
if (sourceJob == null || !deliveryPolicy.allowsComposition(sourceJob, DeliveryPolicySurface.CONVERSATION)) {
115115
return 0;
116116
}
117-
List<Observation> observations = observationRepository.findByAgentJobId(agentJobId);
117+
List<Observation> observations = observationRepository.findByAgentJobId(agentJobId, workspaceId);
118118
if (observations.isEmpty()) {
119119
return 0;
120120
}

0 commit comments

Comments
 (0)