| 1 | package readcoord |
| 2 | |
| 3 | import ( |
| 4 | "time" |
| 5 | |
| 6 | "reasonix/internal/tool" |
| 7 | ) |
| 8 | |
| 9 | // Scope identifies the file a read requirement targets. |
| 10 | type Scope struct { |
| 11 | WorkspaceID string |
| 12 | CanonicalPath string |
| 13 | } |
| 14 | |
| 15 | // State is the lifecycle position of one read requirement. |
| 16 | type State uint8 |
| 17 | |
| 18 | const ( |
| 19 | StateCreated State = iota |
| 20 | StateFetching |
| 21 | StateDelivered |
| 22 | StateSatisfied |
| 23 | StateNeedsMore |
| 24 | StateNeedsScope |
| 25 | StateStale |
| 26 | StateBlocked |
| 27 | StateCancelled |
| 28 | ) |
| 29 | |
| 30 | var stateNames = [...]string{ |
| 31 | StateCreated: "created", |
| 32 | StateFetching: "fetching", |
| 33 | StateDelivered: "delivered", |
| 34 | StateSatisfied: "satisfied", |
| 35 | StateNeedsMore: "needs_more", |
| 36 | StateNeedsScope: "needs_scope", |
| 37 | StateStale: "stale", |
| 38 | StateBlocked: "blocked", |
| 39 | StateCancelled: "cancelled", |
| 40 | } |
| 41 | |
| 42 | func (s State) String() string { |
| 43 | if int(s) < len(stateNames) { |
| 44 | return stateNames[s] |
| 45 | } |
| 46 | return "unknown" |
| 47 | } |
| 48 | |
| 49 | // Terminal reports whether the obligation can no longer be moved by a delivery. |
| 50 | func (s State) Terminal() bool { return s == StateSatisfied || s == StateCancelled } |
| 51 | |
| 52 | // Requirement is what a read must cover to be satisfied. |
| 53 | type Requirement struct { |
| 54 | Intent tool.ReadIntent |
| 55 | // Ranges is the explicit coverage a range requirement must reach. |
| 56 | Ranges []tool.ReadRange |
| 57 | // WholeFile means the requirement covers the file's entire content at the |
| 58 | // obligation's version: only a delivery that reached EOF satisfies it. |
| 59 | WholeFile bool |
| 60 | } |
| 61 | |
| 62 | // Block explains why an obligation stopped and what would resume it. |
| 63 | type Block struct { |
| 64 | Code string |
| 65 | Detail string |
| 66 | Recovery string |
| 67 | } |
| 68 | |
| 69 | // Obligation is one logical read requirement plus the coverage accumulated for |
| 70 | // it. Coverage is only ever accumulated within one content version. |
| 71 | type Obligation struct { |
| 72 | Key string |
| 73 | Scope Scope |
| 74 | Requirement Requirement |
| 75 | State State |
| 76 | // Version is the content version coverage belongs to. A delivery from a |
| 77 | // different version resets coverage instead of extending it. |
| 78 | Version string |
| 79 | Source tool.ReadResultSource |
| 80 | // Covered is the union of delivered ranges on Version, in normalized order. |
| 81 | Covered []tool.ReadRange |
| 82 | // SawEOF reports that some delivery on Version reached the file's end. |
| 83 | SawEOF bool |
| 84 | // SourceEnd is the source's zero-based end line index when a delivery |
| 85 | // established it. Without it, EOF alone proves nothing. |
| 86 | SourceEnd *int |
| 87 | Generation uint64 |
| 88 | Sequence uint64 |
| 89 | Pages int |
| 90 | // Stagnant counts consecutive deliveries that added no new coverage; |
| 91 | // Pivoted records that the one allowed strategy change already happened. |
| 92 | Stagnant int |
| 93 | Pivoted bool |
| 94 | // ActiveTime is the host-measured time spent producing this read's pages. |
| 95 | // Waiting for the model is not counted. |
| 96 | ActiveTime time.Duration |
| 97 | // Stop carries the reason for StateBlocked or StateNeedsScope. |
| 98 | Stop *Block |
| 99 | } |
| 100 | |
| 101 | // clone returns a deep copy so callers can never mutate coordinator state. |
| 102 | func (o *Obligation) clone() Obligation { |
| 103 | out := *o |
| 104 | out.Requirement.Ranges = append([]tool.ReadRange(nil), o.Requirement.Ranges...) |
| 105 | out.Covered = append([]tool.ReadRange(nil), o.Covered...) |
| 106 | if o.SourceEnd != nil { |
| 107 | end := *o.SourceEnd |
| 108 | out.SourceEnd = &end |
| 109 | } |
| 110 | if o.Stop != nil { |
| 111 | stop := *o.Stop |
| 112 | out.Stop = &stop |
| 113 | } |
| 114 | return out |
| 115 | } |
| 116 |