instar 1.3.555 → 1.3.556

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  2. package/dist/core/PostUpdateMigrator.js +15 -0
  3. package/dist/core/PostUpdateMigrator.js.map +1 -1
  4. package/dist/core/types.d.ts +23 -0
  5. package/dist/core/types.d.ts.map +1 -1
  6. package/dist/core/types.js.map +1 -1
  7. package/dist/feedback-factory/inbox/BlobInboxClient.d.ts +75 -0
  8. package/dist/feedback-factory/inbox/BlobInboxClient.d.ts.map +1 -0
  9. package/dist/feedback-factory/inbox/BlobInboxClient.js +121 -0
  10. package/dist/feedback-factory/inbox/BlobInboxClient.js.map +1 -0
  11. package/dist/feedback-factory/inbox/InboxDrainer.d.ts +91 -0
  12. package/dist/feedback-factory/inbox/InboxDrainer.d.ts.map +1 -0
  13. package/dist/feedback-factory/inbox/InboxDrainer.js +188 -0
  14. package/dist/feedback-factory/inbox/InboxDrainer.js.map +1 -0
  15. package/dist/feedback-factory/receiver/BlobInboxStore.d.ts +30 -0
  16. package/dist/feedback-factory/receiver/BlobInboxStore.d.ts.map +1 -0
  17. package/dist/feedback-factory/receiver/BlobInboxStore.js +36 -0
  18. package/dist/feedback-factory/receiver/BlobInboxStore.js.map +1 -0
  19. package/dist/feedback-factory/receiver/handlers.d.ts +18 -3
  20. package/dist/feedback-factory/receiver/handlers.d.ts.map +1 -1
  21. package/dist/feedback-factory/receiver/handlers.js +8 -4
  22. package/dist/feedback-factory/receiver/handlers.js.map +1 -1
  23. package/dist/feedback-factory/store/JsonlFeedbackStore.d.ts +65 -0
  24. package/dist/feedback-factory/store/JsonlFeedbackStore.d.ts.map +1 -0
  25. package/dist/feedback-factory/store/JsonlFeedbackStore.js +194 -0
  26. package/dist/feedback-factory/store/JsonlFeedbackStore.js.map +1 -0
  27. package/dist/scaffold/templates.d.ts.map +1 -1
  28. package/dist/scaffold/templates.js +4 -0
  29. package/dist/scaffold/templates.js.map +1 -1
  30. package/dist/server/AgentServer.d.ts +4 -0
  31. package/dist/server/AgentServer.d.ts.map +1 -1
  32. package/dist/server/AgentServer.js +53 -0
  33. package/dist/server/AgentServer.js.map +1 -1
  34. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  35. package/dist/server/CapabilityIndex.js +11 -0
  36. package/dist/server/CapabilityIndex.js.map +1 -1
  37. package/dist/server/routes.d.ts +4 -0
  38. package/dist/server/routes.d.ts.map +1 -1
  39. package/dist/server/routes.js +10 -0
  40. package/dist/server/routes.js.map +1 -1
  41. package/package.json +1 -1
  42. package/src/data/builtin-manifest.json +64 -64
  43. package/src/scaffold/templates.ts +4 -0
  44. package/upgrades/1.3.556.md +34 -0
  45. package/upgrades/side-effects/feedback-receiver-persistence.md +81 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BlobInboxClient.js","sourceRoot":"","sources":["../../../src/feedback-factory/inbox/BlobInboxClient.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * InboxDrainer.ts — moves accepted fleet feedback from the cloud inbox (Vercel
3
+ * Blob, written by the canonical front) into the durable canonical FeedbackStore
4
+ * on the operated machine.
5
+ *
6
+ * This is the Echo-machine half of the Option-B receiving end: the front makes a
7
+ * report DURABLE the instant it is accepted (no operated machine in the intake
8
+ * critical path); this drainer ingests at its own pace, so a machine asleep /
9
+ * restarting / tunnel-down only DELAYS processing — it never loses a report.
10
+ *
11
+ * Delivery semantics: AT-LEAST-ONCE, made idempotent by the feedbackId dedup the
12
+ * receiver path already has (`store.hasFeedback`). The ordering invariant is
13
+ * delete-AFTER-durable-commit: a blob is removed from the inbox only after the
14
+ * store append has returned. A crash between commit and delete re-delivers the
15
+ * blob on the next tick and dedup drops it.
16
+ *
17
+ * Poison handling: a blob that cannot be parsed/validated is QUARANTINED (re-put
18
+ * under `quarantine/`, then deleted from the inbox) so one malformed object can
19
+ * never wedge the drain loop, and nothing is silently destroyed.
20
+ *
21
+ * Signal-vs-authority: this component holds NO decision authority — every
22
+ * accept/reject decision happened in the front's intake defense chain. The
23
+ * drainer is pure transport + persistence, plus read-only counters for
24
+ * `GET /feedback-inbox/status`.
25
+ */
26
+ import type { BlobInboxClient } from './BlobInboxClient.js';
27
+ import type { FeedbackItem } from '../processor/types.js';
28
+ export interface InboxDrainerOptions {
29
+ client: BlobInboxClient;
30
+ store: {
31
+ hasFeedback(feedbackId: string): boolean;
32
+ addFeedback(item: FeedbackItem): void;
33
+ };
34
+ /** Inbox prefix the front writes under. */
35
+ prefix?: string;
36
+ /** Poll cadence. */
37
+ pollIntervalMs?: number;
38
+ /** Page size per list call. */
39
+ pageSize?: number;
40
+ log?: (msg: string) => void;
41
+ }
42
+ export interface InboxDrainerStatus {
43
+ running: boolean;
44
+ prefix: string;
45
+ pollIntervalMs: number;
46
+ drained: number;
47
+ duplicates: number;
48
+ quarantined: number;
49
+ errors: number;
50
+ ticks: number;
51
+ lastTickAt: string | null;
52
+ lastDrainAt: string | null;
53
+ lastError: string | null;
54
+ }
55
+ export declare class InboxDrainer {
56
+ private readonly client;
57
+ private readonly store;
58
+ private readonly prefix;
59
+ private readonly pollIntervalMs;
60
+ private readonly pageSize;
61
+ private readonly log;
62
+ private timer;
63
+ private ticking;
64
+ private drained;
65
+ private duplicates;
66
+ private quarantined;
67
+ private errors;
68
+ private ticks;
69
+ private lastTickAt;
70
+ private lastDrainAt;
71
+ private lastError;
72
+ constructor(opts: InboxDrainerOptions);
73
+ start(): void;
74
+ stop(): void;
75
+ /**
76
+ * One drain pass: list → (read → ingest → delete) per blob, page by page.
77
+ * Per-blob failures are isolated — one bad object never aborts the pass.
78
+ */
79
+ drainOnce(): Promise<{
80
+ drained: number;
81
+ duplicates: number;
82
+ quarantined: number;
83
+ errors: number;
84
+ }>;
85
+ /** Returns true when the blob was REMOVED from the inbox (drained / duplicate / quarantined). */
86
+ private drainBlob;
87
+ status(): InboxDrainerStatus;
88
+ }
89
+ /** Validate the minimal shape the receiver wrote. Null = quarantine. */
90
+ export declare function parseInboxItem(content: string): FeedbackItem | null;
91
+ //# sourceMappingURL=InboxDrainer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InboxDrainer.d.ts","sourceRoot":"","sources":["../../../src/feedback-factory/inbox/InboxDrainer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAa,MAAM,sBAAsB,CAAC;AACvE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,eAAe,CAAC;IACxB,KAAK,EAAE;QACL,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;QACzC,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAAC;KACvC,CAAC;IACF,2CAA2C;IAC3C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oBAAoB;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+BAA+B;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAMD,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA+B;IACrD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAwB;IAE5C,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,OAAO,CAAS;IAExB,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,SAAS,CAAuB;gBAE5B,IAAI,EAAE,mBAAmB;IASrC,KAAK,IAAI,IAAI;IAUb,IAAI,IAAI,IAAI;IAOZ;;;OAGG;IACG,SAAS,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAqCxG,iGAAiG;YACnF,SAAS;IAoCvB,MAAM,IAAI,kBAAkB;CAe7B;AAED,wEAAwE;AACxE,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAYnE"}
@@ -0,0 +1,188 @@
1
+ /**
2
+ * InboxDrainer.ts — moves accepted fleet feedback from the cloud inbox (Vercel
3
+ * Blob, written by the canonical front) into the durable canonical FeedbackStore
4
+ * on the operated machine.
5
+ *
6
+ * This is the Echo-machine half of the Option-B receiving end: the front makes a
7
+ * report DURABLE the instant it is accepted (no operated machine in the intake
8
+ * critical path); this drainer ingests at its own pace, so a machine asleep /
9
+ * restarting / tunnel-down only DELAYS processing — it never loses a report.
10
+ *
11
+ * Delivery semantics: AT-LEAST-ONCE, made idempotent by the feedbackId dedup the
12
+ * receiver path already has (`store.hasFeedback`). The ordering invariant is
13
+ * delete-AFTER-durable-commit: a blob is removed from the inbox only after the
14
+ * store append has returned. A crash between commit and delete re-delivers the
15
+ * blob on the next tick and dedup drops it.
16
+ *
17
+ * Poison handling: a blob that cannot be parsed/validated is QUARANTINED (re-put
18
+ * under `quarantine/`, then deleted from the inbox) so one malformed object can
19
+ * never wedge the drain loop, and nothing is silently destroyed.
20
+ *
21
+ * Signal-vs-authority: this component holds NO decision authority — every
22
+ * accept/reject decision happened in the front's intake defense chain. The
23
+ * drainer is pure transport + persistence, plus read-only counters for
24
+ * `GET /feedback-inbox/status`.
25
+ */
26
+ const DEFAULT_PREFIX = 'inbox/';
27
+ const DEFAULT_POLL_MS = 60_000;
28
+ const DEFAULT_PAGE_SIZE = 100;
29
+ export class InboxDrainer {
30
+ client;
31
+ store;
32
+ prefix;
33
+ pollIntervalMs;
34
+ pageSize;
35
+ log;
36
+ timer = null;
37
+ ticking = false;
38
+ drained = 0;
39
+ duplicates = 0;
40
+ quarantined = 0;
41
+ errors = 0;
42
+ ticks = 0;
43
+ lastTickAt = null;
44
+ lastDrainAt = null;
45
+ lastError = null;
46
+ constructor(opts) {
47
+ this.client = opts.client;
48
+ this.store = opts.store;
49
+ this.prefix = opts.prefix ?? DEFAULT_PREFIX;
50
+ this.pollIntervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_MS;
51
+ this.pageSize = opts.pageSize ?? DEFAULT_PAGE_SIZE;
52
+ this.log = opts.log ?? (() => { });
53
+ }
54
+ start() {
55
+ if (this.timer)
56
+ return;
57
+ this.timer = setInterval(() => {
58
+ void this.drainOnce();
59
+ }, this.pollIntervalMs);
60
+ this.timer.unref();
61
+ // Prime immediately so a restart doesn't wait a full interval to catch up.
62
+ void this.drainOnce();
63
+ }
64
+ stop() {
65
+ if (this.timer) {
66
+ clearInterval(this.timer);
67
+ this.timer = null;
68
+ }
69
+ }
70
+ /**
71
+ * One drain pass: list → (read → ingest → delete) per blob, page by page.
72
+ * Per-blob failures are isolated — one bad object never aborts the pass.
73
+ */
74
+ async drainOnce() {
75
+ if (this.ticking)
76
+ return { drained: 0, duplicates: 0, quarantined: 0, errors: 0 };
77
+ this.ticking = true;
78
+ const pass = { drained: 0, duplicates: 0, quarantined: 0, errors: 0 };
79
+ try {
80
+ this.ticks++;
81
+ this.lastTickAt = new Date().toISOString();
82
+ // Re-list the FIRST page after each batch instead of cursor-walking: the
83
+ // drain DELETES blobs as it goes, and a cursor taken before the deletes
84
+ // skips entries that shifted into the consumed range. Loop until a page
85
+ // comes back empty or a full page makes zero progress (all blobs failing
86
+ // — they stay queued and retry next tick; the guard caps a pathological
87
+ // store at a bounded number of batches per pass).
88
+ let batches = 0;
89
+ const maxBatches = 1000;
90
+ while (batches++ < maxBatches) {
91
+ const page = await this.client.list(this.prefix, { limit: this.pageSize });
92
+ if (page.blobs.length === 0)
93
+ break;
94
+ let progressed = 0;
95
+ for (const blob of page.blobs) {
96
+ if (await this.drainBlob(blob, pass))
97
+ progressed++;
98
+ }
99
+ if (progressed === 0)
100
+ break;
101
+ }
102
+ this.lastError = null;
103
+ }
104
+ catch (err) {
105
+ // A list-level failure (network, auth) ends the pass; blobs stay queued.
106
+ this.errors++;
107
+ pass.errors++;
108
+ this.lastError = String(err instanceof Error ? err.message : err);
109
+ this.log(`[feedback-inbox] drain pass failed: ${this.lastError}`);
110
+ }
111
+ finally {
112
+ this.ticking = false;
113
+ }
114
+ return pass;
115
+ }
116
+ /** Returns true when the blob was REMOVED from the inbox (drained / duplicate / quarantined). */
117
+ async drainBlob(blob, pass) {
118
+ try {
119
+ const content = await this.client.fetchContent(blob.url);
120
+ const item = parseInboxItem(content);
121
+ if (!item) {
122
+ // Poison object: preserve under quarantine/, then clear from the inbox.
123
+ await this.client.put(`quarantine/${blob.pathname.replace(/^.*?\//, '')}`, content);
124
+ await this.client.del([blob.url]);
125
+ this.quarantined++;
126
+ pass.quarantined++;
127
+ this.log(`[feedback-inbox] quarantined malformed inbox object ${blob.pathname}`);
128
+ return true;
129
+ }
130
+ if (this.store.hasFeedback(item.feedbackId)) {
131
+ this.duplicates++;
132
+ pass.duplicates++;
133
+ }
134
+ else {
135
+ // Durable commit FIRST…
136
+ this.store.addFeedback(item);
137
+ this.drained++;
138
+ pass.drained++;
139
+ this.lastDrainAt = new Date().toISOString();
140
+ }
141
+ // …delete only after the store write returned (at-least-once + dedup).
142
+ await this.client.del([blob.url]);
143
+ return true;
144
+ }
145
+ catch (err) {
146
+ // Leave the blob in place — it re-delivers next tick.
147
+ this.errors++;
148
+ pass.errors++;
149
+ this.lastError = String(err instanceof Error ? err.message : err);
150
+ this.log(`[feedback-inbox] failed to drain ${blob.pathname}: ${this.lastError}`);
151
+ return false;
152
+ }
153
+ }
154
+ status() {
155
+ return {
156
+ running: this.timer !== null,
157
+ prefix: this.prefix,
158
+ pollIntervalMs: this.pollIntervalMs,
159
+ drained: this.drained,
160
+ duplicates: this.duplicates,
161
+ quarantined: this.quarantined,
162
+ errors: this.errors,
163
+ ticks: this.ticks,
164
+ lastTickAt: this.lastTickAt,
165
+ lastDrainAt: this.lastDrainAt,
166
+ lastError: this.lastError,
167
+ };
168
+ }
169
+ }
170
+ /** Validate the minimal shape the receiver wrote. Null = quarantine. */
171
+ export function parseInboxItem(content) {
172
+ let parsed;
173
+ try {
174
+ parsed = JSON.parse(content);
175
+ }
176
+ catch {
177
+ return null;
178
+ }
179
+ if (!parsed || typeof parsed !== 'object')
180
+ return null;
181
+ const row = parsed;
182
+ if (typeof row.feedbackId !== 'string' || row.feedbackId.length === 0)
183
+ return null;
184
+ if (typeof row.title !== 'string' || typeof row.description !== 'string')
185
+ return null;
186
+ return row;
187
+ }
188
+ //# sourceMappingURL=InboxDrainer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InboxDrainer.js","sourceRoot":"","sources":["../../../src/feedback-factory/inbox/InboxDrainer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAkCH,MAAM,cAAc,GAAG,QAAQ,CAAC;AAChC,MAAM,eAAe,GAAG,MAAM,CAAC;AAC/B,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAE9B,MAAM,OAAO,YAAY;IACN,MAAM,CAAkB;IACxB,KAAK,CAA+B;IACpC,MAAM,CAAS;IACf,cAAc,CAAS;IACvB,QAAQ,CAAS;IACjB,GAAG,CAAwB;IAEpC,KAAK,GAA0B,IAAI,CAAC;IACpC,OAAO,GAAG,KAAK,CAAC;IAEhB,OAAO,GAAG,CAAC,CAAC;IACZ,UAAU,GAAG,CAAC,CAAC;IACf,WAAW,GAAG,CAAC,CAAC;IAChB,MAAM,GAAG,CAAC,CAAC;IACX,KAAK,GAAG,CAAC,CAAC;IACV,UAAU,GAAkB,IAAI,CAAC;IACjC,WAAW,GAAkB,IAAI,CAAC;IAClC,SAAS,GAAkB,IAAI,CAAC;IAExC,YAAY,IAAyB;QACnC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACxB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,cAAc,CAAC;QAC5C,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,cAAc,IAAI,eAAe,CAAC;QAC7D,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,iBAAiB,CAAC;QACnD,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACpC,CAAC;IAED,KAAK;QACH,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO;QACvB,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;YAC5B,KAAK,IAAI,CAAC,SAAS,EAAE,CAAC;QACxB,CAAC,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;QACxB,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACnB,2EAA2E;QAC3E,KAAK,IAAI,CAAC,SAAS,EAAE,CAAC;IACxB,CAAC;IAED,IAAI;QACF,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC1B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QACpB,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,SAAS;QACb,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QAClF,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,MAAM,IAAI,GAAG,EAAE,OAAO,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QACtE,IAAI,CAAC;YACH,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,IAAI,CAAC,UAAU,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YAC3C,yEAAyE;YACzE,wEAAwE;YACxE,wEAAwE;YACxE,yEAAyE;YACzE,wEAAwE;YACxE,kDAAkD;YAClD,IAAI,OAAO,GAAG,CAAC,CAAC;YAChB,MAAM,UAAU,GAAG,IAAI,CAAC;YACxB,OAAO,OAAO,EAAE,GAAG,UAAU,EAAE,CAAC;gBAC9B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;gBAC3E,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;oBAAE,MAAM;gBACnC,IAAI,UAAU,GAAG,CAAC,CAAC;gBACnB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;oBAC9B,IAAI,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC;wBAAE,UAAU,EAAE,CAAC;gBACrD,CAAC;gBACD,IAAI,UAAU,KAAK,CAAC;oBAAE,MAAM;YAC9B,CAAC;YACD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACxB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,yEAAyE;YACzE,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YAClE,IAAI,CAAC,GAAG,CAAC,uCAAuC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;QACpE,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACvB,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,iGAAiG;IACzF,KAAK,CAAC,SAAS,CAAC,IAAe,EAAE,IAAkF;QACzH,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACzD,MAAM,IAAI,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;YACrC,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,wEAAwE;gBACxE,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,cAAc,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;gBACpF,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;gBAClC,IAAI,CAAC,WAAW,EAAE,CAAC;gBACnB,IAAI,CAAC,WAAW,EAAE,CAAC;gBACnB,IAAI,CAAC,GAAG,CAAC,uDAAuD,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;gBACjF,OAAO,IAAI,CAAC;YACd,CAAC;YACD,IAAI,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC5C,IAAI,CAAC,UAAU,EAAE,CAAC;gBAClB,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,CAAC;iBAAM,CAAC;gBACN,wBAAwB;gBACxB,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;gBAC7B,IAAI,CAAC,OAAO,EAAE,CAAC;gBACf,IAAI,CAAC,OAAO,EAAE,CAAC;gBACf,IAAI,CAAC,WAAW,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YAC9C,CAAC;YACD,uEAAuE;YACvE,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YAClC,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,sDAAsD;YACtD,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YAClE,IAAI,CAAC,GAAG,CAAC,oCAAoC,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;YACjF,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,MAAM;QACJ,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,KAAK,KAAK,IAAI;YAC5B,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,cAAc,EAAE,IAAI,CAAC,cAAc;YACnC,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,SAAS,EAAE,IAAI,CAAC,SAAS;SAC1B,CAAC;IACJ,CAAC;CACF;AAED,wEAAwE;AACxE,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACvD,MAAM,GAAG,GAAG,MAAiC,CAAC;IAC9C,IAAI,OAAO,GAAG,CAAC,UAAU,KAAK,QAAQ,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACnF,IAAI,OAAO,GAAG,CAAC,KAAK,KAAK,QAAQ,IAAI,OAAO,GAAG,CAAC,WAAW,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACtF,OAAO,GAA8B,CAAC;AACxC,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * BlobInboxStore.ts — the canonical front's ReceiverStore: makes an accepted
3
+ * report DURABLE the instant the intake defenses pass, by writing it as one JSON
4
+ * object into the Vercel Blob inbox (`inbox/<feedbackId>.json`, random-suffixed).
5
+ *
6
+ * This is the cloud half of the Option-B receiving end (migration spec Q2b):
7
+ * the front never depends on any operated machine being awake — durability is
8
+ * Vercel-side; the InboxDrainer on the operated machine ingests asynchronously
9
+ * into the canonical JsonlFeedbackStore.
10
+ *
11
+ * Dedup (`hasFeedback`) is a prefix list against the inbox. It only sees blobs
12
+ * not yet drained — a retransmit AFTER the drainer consumed the original falls
13
+ * through to `addFeedback`, and the drainer's own `store.hasFeedback` dedup drops
14
+ * it at ingest. End-to-end the pipeline is idempotent on feedbackId.
15
+ *
16
+ * No decision authority here: every accept/reject decision lives in
17
+ * handleFeedbackSubmit's ported defense chain. This class only persists.
18
+ */
19
+ import type { BlobInboxClient } from '../inbox/BlobInboxClient.js';
20
+ import type { ReceiverStore } from './handlers.js';
21
+ import type { FeedbackItem } from '../processor/types.js';
22
+ export declare const INBOX_PREFIX = "inbox/";
23
+ export declare class BlobInboxStore implements ReceiverStore {
24
+ private readonly client;
25
+ private readonly clock;
26
+ constructor(client: BlobInboxClient, clock?: () => string);
27
+ hasFeedback(feedbackId: string): Promise<boolean>;
28
+ addFeedback(item: FeedbackItem): Promise<void>;
29
+ }
30
+ //# sourceMappingURL=BlobInboxStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BlobInboxStore.d.ts","sourceRoot":"","sources":["../../../src/feedback-factory/receiver/BlobInboxStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D,eAAO,MAAM,YAAY,WAAW,CAAC;AAErC,qBAAa,cAAe,YAAW,aAAa;IAEhD,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK;gBADL,MAAM,EAAE,eAAe,EACvB,KAAK,GAAE,MAAM,MAAuC;IAGjE,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKjD,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;CAIrD"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * BlobInboxStore.ts — the canonical front's ReceiverStore: makes an accepted
3
+ * report DURABLE the instant the intake defenses pass, by writing it as one JSON
4
+ * object into the Vercel Blob inbox (`inbox/<feedbackId>.json`, random-suffixed).
5
+ *
6
+ * This is the cloud half of the Option-B receiving end (migration spec Q2b):
7
+ * the front never depends on any operated machine being awake — durability is
8
+ * Vercel-side; the InboxDrainer on the operated machine ingests asynchronously
9
+ * into the canonical JsonlFeedbackStore.
10
+ *
11
+ * Dedup (`hasFeedback`) is a prefix list against the inbox. It only sees blobs
12
+ * not yet drained — a retransmit AFTER the drainer consumed the original falls
13
+ * through to `addFeedback`, and the drainer's own `store.hasFeedback` dedup drops
14
+ * it at ingest. End-to-end the pipeline is idempotent on feedbackId.
15
+ *
16
+ * No decision authority here: every accept/reject decision lives in
17
+ * handleFeedbackSubmit's ported defense chain. This class only persists.
18
+ */
19
+ export const INBOX_PREFIX = 'inbox/';
20
+ export class BlobInboxStore {
21
+ client;
22
+ clock;
23
+ constructor(client, clock = () => new Date().toISOString()) {
24
+ this.client = client;
25
+ this.clock = clock;
26
+ }
27
+ async hasFeedback(feedbackId) {
28
+ const page = await this.client.list(`${INBOX_PREFIX}${feedbackId}`, { limit: 1 });
29
+ return page.blobs.length > 0;
30
+ }
31
+ async addFeedback(item) {
32
+ const row = { receivedAt: this.clock(), status: 'unprocessed', ...item };
33
+ await this.client.put(`${INBOX_PREFIX}${item.feedbackId}.json`, JSON.stringify(row));
34
+ }
35
+ }
36
+ //# sourceMappingURL=BlobInboxStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BlobInboxStore.js","sourceRoot":"","sources":["../../../src/feedback-factory/receiver/BlobInboxStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAMH,MAAM,CAAC,MAAM,YAAY,GAAG,QAAQ,CAAC;AAErC,MAAM,OAAO,cAAc;IAEN;IACA;IAFnB,YACmB,MAAuB,EACvB,QAAsB,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QADpD,WAAM,GAAN,MAAM,CAAiB;QACvB,UAAK,GAAL,KAAK,CAA+C;IACpE,CAAC;IAEJ,KAAK,CAAC,WAAW,CAAC,UAAkB;QAClC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,YAAY,GAAG,UAAU,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;QAClF,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,IAAkB;QAClC,MAAM,GAAG,GAAiB,EAAE,UAAU,EAAE,IAAI,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,IAAI,EAAE,CAAC;QACvF,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,YAAY,GAAG,IAAI,CAAC,UAAU,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IACvF,CAAC;CACF"}
@@ -26,8 +26,19 @@ export interface FeedbackResponse {
26
26
  headers?: Record<string, string>;
27
27
  json: unknown;
28
28
  }
29
+ /**
30
+ * The narrow store surface the SUBMIT path actually uses (dedup + write). The full
31
+ * `FeedbackStore` satisfies it structurally; the canonical front's Blob-backed
32
+ * inbox store implements it asynchronously (Vercel Blob is REST). Methods may be
33
+ * sync or async — the handler awaits both, so decision order, status codes, and
34
+ * messages are unchanged for the existing synchronous stores.
35
+ */
36
+ export interface ReceiverStore {
37
+ hasFeedback(feedbackId: string): boolean | Promise<boolean>;
38
+ addFeedback(item: Parameters<FeedbackStore['addFeedback']>[0]): void | Promise<void>;
39
+ }
29
40
  export interface FeedbackHandlerDeps {
30
- store: FeedbackStore;
41
+ store: ReceiverStore;
31
42
  rateLimiter: RateLimiter;
32
43
  /** Normalized webhook secret (see normalizeWebhookSecret); undefined ⇒ unsigned-but-accepted. */
33
44
  secret: string | undefined;
@@ -36,6 +47,10 @@ export interface FeedbackHandlerDeps {
36
47
  /** Injected id generator (defaults to the reference's `fb-<base36 time>-<rand>`). */
37
48
  generateFeedbackId?: () => string;
38
49
  }
39
- /** Port of handleSubmit. Returns the status/headers/json the canonical front sends back. */
40
- export declare function handleFeedbackSubmit(req: FeedbackRequest, deps: FeedbackHandlerDeps): FeedbackResponse;
50
+ /**
51
+ * Port of handleSubmit. Returns the status/headers/json the canonical front sends
52
+ * back. Async because the canonical front's store is REST-backed (Vercel Blob);
53
+ * for synchronous stores the awaits are no-ops and behavior is byte-identical.
54
+ */
55
+ export declare function handleFeedbackSubmit(req: FeedbackRequest, deps: FeedbackHandlerDeps): Promise<FeedbackResponse>;
41
56
  //# sourceMappingURL=handlers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../../src/feedback-factory/receiver/handlers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;IACvD,IAAI,EAAE,OAAO,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,aAAa,CAAC;IACrB,WAAW,EAAE,WAAW,CAAC;IACzB,iGAAiG;IACjG,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,sDAAsD;IACtD,GAAG,EAAE,MAAM,CAAC;IACZ,qFAAqF;IACrF,kBAAkB,CAAC,EAAE,MAAM,MAAM,CAAC;CACnC;AAWD,4FAA4F;AAC5F,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,eAAe,EAAE,IAAI,EAAE,mBAAmB,GAAG,gBAAgB,CAsFtG"}
1
+ {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../../src/feedback-factory/receiver/handlers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;IACvD,IAAI,EAAE,OAAO,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,IAAI,EAAE,OAAO,CAAC;CACf;AAED;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5D,WAAW,CAAC,IAAI,EAAE,UAAU,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtF;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,aAAa,CAAC;IACrB,WAAW,EAAE,WAAW,CAAC;IACzB,iGAAiG;IACjG,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,sDAAsD;IACtD,GAAG,EAAE,MAAM,CAAC;IACZ,qFAAqF;IACrF,kBAAkB,CAAC,EAAE,MAAM,MAAM,CAAC;CACnC;AAWD;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,eAAe,EAAE,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAsFrH"}
@@ -22,8 +22,12 @@ function header(headers, name) {
22
22
  function defaultFeedbackId(now) {
23
23
  return `fb-${now.toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
24
24
  }
25
- /** Port of handleSubmit. Returns the status/headers/json the canonical front sends back. */
26
- export function handleFeedbackSubmit(req, deps) {
25
+ /**
26
+ * Port of handleSubmit. Returns the status/headers/json the canonical front sends
27
+ * back. Async because the canonical front's store is REST-backed (Vercel Blob);
28
+ * for synchronous stores the awaits are no-ops and behavior is byte-identical.
29
+ */
30
+ export async function handleFeedbackSubmit(req, deps) {
27
31
  const { store, rateLimiter, secret, now } = deps;
28
32
  const sourceIp = extractSourceIp(req.headers, req.remoteAddress);
29
33
  // Layer 1: rate limit.
@@ -82,10 +86,10 @@ export function handleFeedbackSubmit(req, deps) {
82
86
  ? provided
83
87
  : (deps.generateFeedbackId ?? (() => defaultFeedbackId(now)))();
84
88
  // Layer 5: dedup — idempotent on a known feedbackId.
85
- if (store.hasFeedback(feedbackId)) {
89
+ if (await store.hasFeedback(feedbackId)) {
86
90
  return { status: 200, json: { id: feedbackId, received: true, duplicate: true } };
87
91
  }
88
- store.addFeedback({
92
+ await store.addFeedback({
89
93
  feedbackId,
90
94
  type,
91
95
  title: title.trim().slice(0, 500),
@@ -1 +1 @@
1
- {"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../../src/feedback-factory/receiver/handlers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../../src/feedback-factory/receiver/handlers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * JsonlFeedbackStore.ts — the DURABLE canonical FeedbackStore for the operated
3
+ * instance (post-cutover authority under the migration spec's Option B: Echo's
4
+ * instance keeps its OWN canonical copy; Portal never opens a write door).
5
+ *
6
+ * Backing format: newline-delimited JSON, one file per entity family
7
+ * (`feedback.jsonl`, `clusters.jsonl`, `dispatches.jsonl`) under a caller-supplied
8
+ * directory. The format is DELIBERATELY the same shape `PersistedShadowImportTarget`
9
+ * writes (one full row per line, keyed by feedbackId/clusterId), so the proven AS-IS
10
+ * import artifact seeds this store directly — the cutover import output IS the
11
+ * canonical store, no translation step (fewer integrity risks at the one-way door).
12
+ *
13
+ * Mutation model: append-only log with LAST-WRITE-WINS on load. Every mutation
14
+ * appends the FULL updated row; on construction all lines are folded into a Map by
15
+ * id (later lines supersede earlier). A file with one row per entity — exactly what
16
+ * the import produces — is therefore a valid, already-compact log. When superseded
17
+ * lines pile up past a threshold the file is compacted at construction time via an
18
+ * atomic temp+rename rewrite (crash mid-compaction leaves the original intact).
19
+ *
20
+ * JSONL (not SQLite) for the same reason as PersistedShadowImportTarget: dependency-
21
+ * free, no native module, trivially inspectable, and append durability is a plain
22
+ * appendFileSync. Volume is fleet feedback — low write rate; the import's measured
23
+ * 148k-row artifact parses in well under a second.
24
+ */
25
+ import type { FeedbackItem, Cluster } from '../processor/types.js';
26
+ import type { ReopenDecision } from '../processor/reopen.js';
27
+ import type { DispatchRecord } from '../dispatch/dispatch.js';
28
+ import type { FeedbackStore, FeedbackMetrics } from './FeedbackStore.js';
29
+ export declare class JsonlFeedbackStore implements FeedbackStore {
30
+ private readonly dir;
31
+ private readonly feedbackPath;
32
+ private readonly clustersPath;
33
+ private readonly dispatchesPath;
34
+ private feedback;
35
+ private clusters;
36
+ private dispatches;
37
+ private counts;
38
+ constructor(dir: string);
39
+ private loadAndMaybeCompact;
40
+ private appendFeedback;
41
+ private appendCluster;
42
+ getUnprocessedFeedback(): FeedbackItem[];
43
+ getActiveClusters(): Cluster[];
44
+ getCluster(clusterId: string): Cluster | undefined;
45
+ upsertClusterFromItem(clusterId: string, item: FeedbackItem): void;
46
+ mergeIntoCluster(clusterId: string, _item: FeedbackItem): void;
47
+ applyReopen(clusterId: string, decision: ReopenDecision): void;
48
+ markProcessed(feedbackId: string, clusterId: string): void;
49
+ hasFeedback(feedbackId: string): boolean;
50
+ addFeedback(item: FeedbackItem): void;
51
+ listDispatches(filter?: {
52
+ since?: string;
53
+ type?: string;
54
+ }): DispatchRecord[];
55
+ findDispatchByTitle(title: string): DispatchRecord | undefined;
56
+ createDispatch(record: DispatchRecord): void;
57
+ metrics(): FeedbackMetrics;
58
+ /** Read-only size snapshot for the status surface. */
59
+ sizes(): {
60
+ feedback: number;
61
+ clusters: number;
62
+ dispatches: number;
63
+ };
64
+ }
65
+ //# sourceMappingURL=JsonlFeedbackStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JsonlFeedbackStore.d.ts","sourceRoot":"","sources":["../../../src/feedback-factory/store/JsonlFeedbackStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAWzE,qBAAa,kBAAmB,YAAW,aAAa;IAU1C,OAAO,CAAC,QAAQ,CAAC,GAAG;IAThC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IAExC,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,UAAU,CAA8B;IAChD,OAAO,CAAC,MAAM,CAAwE;gBAEzD,GAAG,EAAE,MAAM;IAiBxC,OAAO,CAAC,mBAAmB;IAe3B,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,aAAa;IAMrB,sBAAsB,IAAI,YAAY,EAAE;IAMxC,iBAAiB,IAAI,OAAO,EAAE;IAI9B,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS;IAIlD,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI;IAmBlE,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI;IAS9D,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,IAAI;IAY9D,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAU1D,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAIxC,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAMrC,cAAc,CAAC,MAAM,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,cAAc,EAAE;IAQ5E,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAI9D,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI;IAM5C,OAAO,IAAI,eAAe;IAI1B,sDAAsD;IACtD,KAAK,IAAI;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE;CAGpE"}