Skip to content

Commit 22ededc

Browse files
committed
fix: Fix holon linking in decide phase (DRR)
1 parent ee32616 commit 22ededc

File tree

6 files changed

+57
-9
lines changed

6 files changed

+57
-9
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- **DRR Linking (FPF E.9)**: Decision records now create graph relations to hypotheses.
13+
- `quint_decide` accepts new `rejected_ids` parameter for rejected alternatives.
14+
- Creates `selects` relation: DRR → winner hypothesis.
15+
- Creates `rejects` relations: DRR → each rejected alternative.
16+
- Enables queries: "What alternatives were considered for this DRR?"
17+
1218
- **sqlc Integration**: Type-safe database queries generated from SQL.
1319
- All database operations now use sqlc-generated code with proper type safety.
1420
- New `db/store.go` wrapper provides clean API while preserving schema bootstrap.

src/mcp/cmd/commands/q5-decide.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ Computes R_eff for comparison.
7272
Finalizes the decision and creates the DRR.
7373
- **title**: Title of the decision (e.g., "Use Redis for Caching").
7474
- **winner_id**: The ID of the chosen hypothesis.
75+
- **rejected_ids**: Array of IDs of rejected L2 alternatives (creates `rejects` relations).
7576
- **context**: The problem statement.
7677
- **decision**: "We decided to use [Winner] because..."
7778
- **rationale**: "It had the highest R_eff and best fit for constraints..."
@@ -95,10 +96,21 @@ Presenting comparison:
9596
9697
[User responds: "redis-caching"]
9798
98-
[Call quint_decide(winner_id="redis-caching", title="Use Redis for Caching", ...)]
99+
[Call quint_decide(
100+
title="Use Redis for Caching",
101+
winner_id="redis-caching",
102+
rejected_ids=["cdn-edge"],
103+
context="...",
104+
decision="...",
105+
rationale="...",
106+
consequences="..."
107+
)]
99108
→ DRR created at .quint/decisions/DRR-XXXX-use-redis-for-caching.md
109+
→ Relations created:
110+
- DRR --selects--> redis-caching
111+
- DRR --rejects--> cdn-edge
100112
101-
Result: Decision recorded. Ready for implementation.
113+
Result: Decision recorded with full audit trail. Ready for implementation.
102114
```
103115

104116
## Example: Failure Path (Transformer Mandate Violation)

src/mcp/internal/fpf/integration_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ func TestFullFPFWorkflowIntegration(t *testing.T) {
321321
t.Fatalf("SaveState failed: %v", err)
322322
}
323323

324-
path, err := tools.FinalizeDecision("Final Decision", finalWinnerID, "Context", "Decision", drrContent, "Consequences", "Characteristics")
324+
path, err := tools.FinalizeDecision("Final Decision", finalWinnerID, nil, "Context", "Decision", drrContent, "Consequences", "Characteristics")
325325
if err != nil {
326326
t.Fatalf("FinalizeDecision failed: %v", err)
327327
}

src/mcp/internal/fpf/server.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,13 @@ func (s *Server) handleToolsList(req JSONRPCRequest) {
225225
InputSchema: map[string]interface{}{
226226
"type": "object",
227227
"properties": map[string]interface{}{
228-
"title": map[string]string{"type": "string"},
229-
"winner_id": map[string]string{"type": "string"},
228+
"title": map[string]string{"type": "string"},
229+
"winner_id": map[string]string{"type": "string"},
230+
"rejected_ids": map[string]interface{}{
231+
"type": "array",
232+
"items": map[string]string{"type": "string"},
233+
"description": "IDs of rejected L2 alternatives",
234+
},
230235
"context": map[string]string{"type": "string"},
231236
"decision": map[string]string{"type": "string"},
232237
"rationale": map[string]string{"type": "string"},
@@ -402,7 +407,15 @@ func (s *Server) handleToolsCall(req JSONRPCRequest) {
402407

403408
case "quint_decide":
404409
s.tools.FSM.State.Phase = PhaseDecision
405-
output, err = s.tools.FinalizeDecision(arg("title"), arg("winner_id"), arg("context"), arg("decision"), arg("rationale"), arg("consequences"), arg("characteristics"))
410+
var rejectedIDs []string
411+
if rids, ok := params.Arguments["rejected_ids"].([]interface{}); ok {
412+
for _, r := range rids {
413+
if s, ok := r.(string); ok {
414+
rejectedIDs = append(rejectedIDs, s)
415+
}
416+
}
417+
}
418+
output, err = s.tools.FinalizeDecision(arg("title"), arg("winner_id"), rejectedIDs, arg("context"), arg("decision"), arg("rationale"), arg("consequences"), arg("characteristics"))
406419
if err == nil {
407420
s.tools.FSM.State.Phase = PhaseIdle
408421
if saveErr := s.tools.FSM.SaveState(s.tools.GetFPFDir() + "/state.json"); saveErr != nil {

src/mcp/internal/fpf/tools.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,7 @@ func (t *Tools) RefineLoopback(currentPhase Phase, parentID, insight, newTitle,
481481
return childPath, nil
482482
}
483483

484-
func (t *Tools) FinalizeDecision(title, winnerID, decisionContext, decision, rationale, consequences, characteristics string) (string, error) {
484+
func (t *Tools) FinalizeDecision(title, winnerID string, rejectedIDs []string, decisionContext, decision, rationale, consequences, characteristics string) (string, error) {
485485
defer t.RecordWork("FinalizeDecision", time.Now())
486486

487487
body := fmt.Sprintf("\n# %s\n\n", title)
@@ -510,10 +510,27 @@ func (t *Tools) FinalizeDecision(title, winnerID, decisionContext, decision, rat
510510
}
511511

512512
if t.DB != nil {
513+
ctx := context.Background()
513514
drrID := t.Slugify(title)
514-
if err := t.DB.CreateHolon(context.Background(), drrID, "DRR", "", "DRR", title, body, "default", "", winnerID); err != nil {
515+
if err := t.DB.CreateHolon(ctx, drrID, "DRR", "", "DRR", title, body, "default", "", winnerID); err != nil {
515516
fmt.Fprintf(os.Stderr, "Warning: failed to create DRR holon in DB: %v\n", err)
516517
}
518+
519+
// Create selects relation: DRR → winner
520+
if winnerID != "" {
521+
if err := t.createRelation(ctx, drrID, "selects", winnerID, 3); err != nil {
522+
fmt.Fprintf(os.Stderr, "Warning: failed to create selects relation: %v\n", err)
523+
}
524+
}
525+
526+
// Create rejects relations: DRR → each rejected alternative
527+
for _, rejID := range rejectedIDs {
528+
if rejID != "" && rejID != winnerID {
529+
if err := t.createRelation(ctx, drrID, "rejects", rejID, 3); err != nil {
530+
fmt.Fprintf(os.Stderr, "Warning: failed to create rejects relation to %s: %v\n", rejID, err)
531+
}
532+
}
533+
}
517534
}
518535

519536
if winnerID != "" {

src/mcp/internal/fpf/tools_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ func TestFinalizeDecision(t *testing.T) {
283283
title := "Final Project Decision"
284284
content := "This is the DRR content for the decision."
285285

286-
drrPath, err := tools.FinalizeDecision(title, winnerID, "Context", content, "Rationale", "Consequences", "Characteristics")
286+
drrPath, err := tools.FinalizeDecision(title, winnerID, nil, "Context", content, "Rationale", "Consequences", "Characteristics")
287287
if err != nil {
288288
t.Fatalf("FinalizeDecision failed: %v", err)
289289
}

0 commit comments

Comments
 (0)