-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathtypes.go
More file actions
345 lines (317 loc) · 10.3 KB
/
Copy pathtypes.go
File metadata and controls
345 lines (317 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
// / ctx: https://ctx.ist
// ,'`./ do you remember?
// `.,'\
// \ Copyright 2026-present Context contributors.
// SPDX-License-Identifier: Apache-2.0
package hub
import (
"encoding/json"
"sync"
"time"
cfgHub "github.com/ActiveMemory/ctx/internal/config/hub"
"github.com/hashicorp/raft"
"google.golang.org/grpc"
)
// Entry is the unit of sharing in the hub.
//
// Every published piece of context is an Entry. Entries are
// append-only: once published, never modified or deleted.
// Each entry gets a monotonically increasing sequence number
// assigned by the hub.
//
// Fields:
// - ID: UUID, globally unique
// - Type: entry type (decision, learning, convention, task)
// - Content: the actual text (markdown)
// - Origin: project name (server-authoritative; stamped
// from the authenticated client's ProjectName, never
// trusted from client input)
// - Timestamp: when it was published
// - Sequence: monotonic counter, assigned by hub on publish
// - Meta: client-advisory hints. NOT authoritative
// attribution. See [EntryMeta] and the decision record
// at .context/DECISIONS.md [2026-04-11-180000].
type Entry struct {
ID string `json:"id"`
Type string `json:"type"`
Content string `json:"content"`
Origin string `json:"origin"`
Timestamp time.Time `json:"timestamp"`
Sequence uint64 `json:"sequence"`
Meta EntryMeta `json:"meta"`
}
// EntryMeta holds client-advisory metadata attached to a
// published entry. Fields in this struct are NEVER used
// for authoritative attribution: they carry display
// labels, observability hints, and human-readable
// context that the server has no way to verify.
//
// The renderer on the client side MUST label these values
// as "client-reported" or similar when surfacing them, so
// readers cannot confuse them with server-authoritative
// fields like [Entry.Origin].
//
// All fields are optional. Size and character
// restrictions are enforced by [validateEntryMeta] at
// publish time.
//
// Fields:
// - DisplayName: human-readable label (e.g. "Alice")
// often different from the project name that Origin
// carries. Never trusted for security.
// - Host: machine or CI runner name ("laptop-01",
// "gh-runner-42"). Observability hint.
// - Tool: publishing tool identifier
// ("ctx@0.8.1", "my-script@v2"). Observability hint.
// - Via: upstream provenance
// ("github-actions", "nightly-cron"). Observability hint.
type EntryMeta struct {
DisplayName string `json:"display_name,omitempty"`
Host string `json:"host,omitempty"`
Tool string `json:"tool,omitempty"`
Via string `json:"via,omitempty"`
}
// ClientInfo holds registration data for a connected client.
//
// Fields:
// - ID: unique client identifier (UUID)
// - ProjectName: name of the project this client represents
// - Token: bearer token for authenticating RPCs
type ClientInfo struct {
ID string `json:"id"`
ProjectName string `json:"project_name"`
Token string `json:"token"`
}
// Meta holds hub-level metadata persisted alongside the log.
//
// Fields:
// - SequenceCounter: next sequence number to assign
// - CreatedAt: when the hub was first started
type Meta struct {
SequenceCounter uint64 `json:"sequence_counter"`
CreatedAt time.Time `json:"created_at"`
}
// Store is an append-only JSONL storage backend for entries.
//
// All writes are serialized via a mutex. Entries are appended
// to a single JSONL file. Client registry and metadata are
// stored as separate JSON files.
//
// Fields:
// - dir: directory where data files live
// - mu: serializes all reads and writes
// - meta: hub-level metadata (sequence counter)
// - clients: registered client tokens
// - tokenIdx: token-to-client index for O(1) lookup
// - entries: in-memory cache of all entries (append-only)
type Store struct {
dir string
mu sync.Mutex
meta Meta
clients []ClientInfo
tokenIdx map[string]int
entries []Entry
}
// Server is the ctx Hub gRPC server.
//
// It implements Register, Publish, Sync, Listen, and Status
// RPCs backed by an append-only [Store].
//
// Fields:
// - store: append-only storage backend
// - adminToken: token required for Register RPC
// - grpc: underlying gRPC server
// - listeners: fan-out broadcaster for Listen streams
// - cluster: optional Raft cluster for HA
type Server struct {
store *Store
adminToken string
grpc *grpc.Server
listeners *fanOut
cluster *Cluster
}
// fanOut manages real-time entry broadcast to listeners.
//
// Fields:
// - mu: serializes subscribe/unsubscribe/broadcast
// - subs: active listener channels
// - dropped: count of disconnected slow listeners; accessed
// with sync/atomic so readers on other goroutines (the
// Status RPC handler) never take the broadcast mutex
type fanOut struct {
mu sync.Mutex
subs map[chan []Entry]struct{}
dropped uint64
}
// RegisterRequest is the input for the Register RPC.
//
// Fields:
// - AdminToken: admin token from server startup
// - ProjectName: this project's identifier
type RegisterRequest struct {
AdminToken string `json:"admin_token"`
ProjectName string `json:"project_name"`
}
// RegisterResponse is the output of the Register RPC.
//
// Fields:
// - ClientID: assigned client identifier
// - ClientToken: token for future RPCs
type RegisterResponse struct {
ClientID string `json:"client_id"`
ClientToken string `json:"client_token"`
}
// RevokeRequest is the input for the Revoke RPC.
//
// Fields:
// - AdminToken: admin token from server startup
// - ClientID: ID of the client whose token to revoke
type RevokeRequest struct {
AdminToken string `json:"admin_token"`
ClientID string `json:"client_id"`
}
// RevokeResponse is the output of the Revoke RPC. It carries
// no fields; a nil error signals the client was revoked.
type RevokeResponse struct{}
// PublishRequest is the input for the Publish RPC.
//
// Fields:
// - Entries: entries to publish
type PublishRequest struct {
Entries []PublishEntry `json:"entries"`
}
// PublishEntry is a single entry in a PublishRequest.
//
// Fields:
// - ID: entry UUID
// - Type: entry type
// - Content: markdown text
// - Origin: source project name as claimed by the
// client. The server ignores this value and stamps
// the stored Entry.Origin from the authenticated
// client identity instead. Kept on the wire for
// client-side round-tripping only.
// - Timestamp: Unix epoch seconds
// - Meta: optional client-advisory hints. Round-tripped
// verbatim (subject to validateEntryMeta size and
// character limits), never promoted to
// authoritative attribution.
type PublishEntry struct {
ID string `json:"id"`
Type string `json:"type"`
Content string `json:"content"`
Origin string `json:"origin"`
Timestamp int64 `json:"timestamp"`
Meta EntryMeta `json:"meta"`
}
// PublishResponse is the output of the Publish RPC.
//
// Fields:
// - Sequences: assigned sequence numbers
type PublishResponse struct {
Sequences []uint64 `json:"sequences"`
}
// SyncRequest is the input for the Sync RPC.
//
// Fields:
// - Types: entry types to sync (empty = all)
// - SinceSequence: return entries after this sequence
type SyncRequest struct {
Types []string `json:"types"`
SinceSequence uint64 `json:"since_sequence"`
}
// ListenRequest is the input for the Listen RPC.
//
// Fields:
// - Types: entry types to receive (empty = all)
// - SinceSequence: start from this sequence
type ListenRequest struct {
Types []string `json:"types"`
SinceSequence uint64 `json:"since_sequence"`
}
// EntryMsg is a wire-format entry for streaming RPCs
// (Sync and Listen responses).
//
// Fields:
// - ID: entry UUID
// - Type: entry type
// - Content: markdown text
// - Origin: server-authoritative source project
// - Timestamp: Unix epoch seconds
// - Sequence: hub-assigned sequence
// - Meta: client-advisory hints forwarded to readers
type EntryMsg struct {
ID string `json:"id"`
Type string `json:"type"`
Content string `json:"content"`
Origin string `json:"origin"`
Timestamp int64 `json:"timestamp"`
Sequence uint64 `json:"sequence"`
Meta EntryMeta `json:"meta"`
}
// StatusResponse is the output of the Status RPC.
//
// Fields:
// - TotalEntries: total number of entries
// - ConnectedClients: active listener count
// - DroppedListeners: cumulative slow-listener disconnects
// - EntriesByType: entry count per type
// - EntriesByProject: entry count per origin project
type StatusResponse struct {
TotalEntries uint64 `json:"total_entries"`
ConnectedClients uint32 `json:"connected_clients"`
DroppedListeners uint64 `json:"dropped_listeners"`
EntriesByType map[string]uint64 `json:"entries_by_type"`
EntriesByProject map[string]uint64 `json:"entries_by_project"`
}
// Client is a gRPC client for the ctx Hub.
//
// Fields:
// - conn: underlying gRPC connection
// - token: bearer token for authenticated RPCs
type Client struct {
conn *grpc.ClientConn
token string
}
// Cluster wraps a Raft node for leader election only.
//
// Fields:
// - raftNode: the underlying Raft instance
// - transport: network transport for Raft communication
type Cluster struct {
raftNode *raft.Raft
transport *raft.NetworkTransport
}
// jsonCodec is a gRPC codec using JSON encoding instead of
// protobuf. This allows plain Go structs as RPC messages
// without generated protobuf code.
type jsonCodec struct{}
// codecName is the gRPC content-subtype for the JSON codec.
const codecName = cfgHub.StructTagJSON
// Marshal encodes v as JSON.
//
// Parameters:
// - v: value to encode
//
// Returns:
// - []byte: JSON-encoded bytes
// - error: non-nil if encoding fails
func (jsonCodec) Marshal(v any) ([]byte, error) {
return json.Marshal(v)
}
// Unmarshal decodes JSON data into v.
//
// Parameters:
// - data: JSON bytes to decode
// - v: target to decode into
//
// Returns:
// - error: non-nil if decoding fails
func (jsonCodec) Unmarshal(data []byte, v any) error {
return json.Unmarshal(data, v)
}
// Name returns the codec content-subtype.
//
// Returns:
// - string: cfgHub.StructTagJSON
func (jsonCodec) Name() string { return codecName }