instar 1.3.20 → 1.3.21

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.
@@ -0,0 +1,26 @@
1
+ /**
2
+ * process.ts — the feedback-factory processing composition.
3
+ *
4
+ * Ties the already-parity'd pure pieces together over a FeedbackStore, mirroring
5
+ * the reference's cmd_cluster (decide) + cmd_apply_clusters (write) run back-to-back:
6
+ *
7
+ * 1. read unprocessed items + active clusters from the store
8
+ * 2. clusterItems() decides merge/create per item (parity'd, increment 4)
9
+ * 3. apply each decision to the store (create / merge); when a merge carries a
10
+ * "possible regression" note, computeReopen() (parity'd, increment 8) decides
11
+ * the reopen and applyReopen writes it
12
+ * 4. mark each item processed; counters update for observability
13
+ *
14
+ * The decision logic is already proven equivalent to the reference; this is the
15
+ * orchestration over the injected store, verified by the Tier-2 integration test
16
+ * against InMemoryFeedbackStore.
17
+ */
18
+ import type { ClusterResult } from './types.js';
19
+ import type { FeedbackStore, FeedbackMetrics } from '../store/FeedbackStore.js';
20
+ export interface ProcessResult {
21
+ results: ClusterResult[];
22
+ metrics: FeedbackMetrics;
23
+ }
24
+ /** Run one clustering+apply pass over the store. `now` injected for the reopen audit note. */
25
+ export declare function processUnprocessed(store: FeedbackStore, now: string): ProcessResult;
26
+ //# sourceMappingURL=process.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../../src/feedback-factory/processor/process.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAEhF,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,OAAO,EAAE,eAAe,CAAC;CAC1B;AAED,8FAA8F;AAC9F,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,aAAa,CA2BnF"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * process.ts — the feedback-factory processing composition.
3
+ *
4
+ * Ties the already-parity'd pure pieces together over a FeedbackStore, mirroring
5
+ * the reference's cmd_cluster (decide) + cmd_apply_clusters (write) run back-to-back:
6
+ *
7
+ * 1. read unprocessed items + active clusters from the store
8
+ * 2. clusterItems() decides merge/create per item (parity'd, increment 4)
9
+ * 3. apply each decision to the store (create / merge); when a merge carries a
10
+ * "possible regression" note, computeReopen() (parity'd, increment 8) decides
11
+ * the reopen and applyReopen writes it
12
+ * 4. mark each item processed; counters update for observability
13
+ *
14
+ * The decision logic is already proven equivalent to the reference; this is the
15
+ * orchestration over the injected store, verified by the Tier-2 integration test
16
+ * against InMemoryFeedbackStore.
17
+ */
18
+ import { clusterItems } from './cluster.js';
19
+ import { computeReopen } from './reopen.js';
20
+ /** Run one clustering+apply pass over the store. `now` injected for the reopen audit note. */
21
+ export function processUnprocessed(store, now) {
22
+ const items = store.getUnprocessedFeedback();
23
+ const clusters = store.getActiveClusters();
24
+ const itemById = new Map(items.map((i) => [i.feedbackId, i]));
25
+ const results = clusterItems(items, clusters);
26
+ for (const r of results) {
27
+ const item = itemById.get(r.feedbackId);
28
+ if (!item)
29
+ continue;
30
+ if (r.action === 'create') {
31
+ store.upsertClusterFromItem(r.clusterId, item);
32
+ }
33
+ else {
34
+ store.mergeIntoCluster(r.clusterId, item);
35
+ // A merge that flagged a possible regression auto-reopens the cluster.
36
+ if (r.note && r.note.includes('possible regression')) {
37
+ const cluster = store.getCluster(r.clusterId);
38
+ if (cluster) {
39
+ store.applyReopen(r.clusterId, computeReopen(cluster, r.feedbackId, now));
40
+ }
41
+ }
42
+ }
43
+ store.markProcessed(r.feedbackId, r.clusterId);
44
+ }
45
+ return { results, metrics: store.metrics() };
46
+ }
47
+ //# sourceMappingURL=process.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process.js","sourceRoot":"","sources":["../../../src/feedback-factory/processor/process.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAS5C,8FAA8F;AAC9F,MAAM,UAAU,kBAAkB,CAAC,KAAoB,EAAE,GAAW;IAClE,MAAM,KAAK,GAAG,KAAK,CAAC,sBAAsB,EAAE,CAAC;IAC7C,MAAM,QAAQ,GAAG,KAAK,CAAC,iBAAiB,EAAE,CAAC;IAC3C,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAE9D,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IAE9C,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;QACxC,IAAI,CAAC,IAAI;YAAE,SAAS;QAEpB,IAAI,CAAC,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC1B,KAAK,CAAC,qBAAqB,CAAC,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QACjD,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAC1C,uEAAuE;YACvE,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,qBAAqB,CAAC,EAAE,CAAC;gBACrD,MAAM,OAAO,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;gBAC9C,IAAI,OAAO,EAAE,CAAC;oBACZ,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,EAAE,aAAa,CAAC,OAAO,EAAE,CAAC,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC;gBAC5E,CAAC;YACH,CAAC;QACH,CAAC;QACD,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC;IACjD,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * FeedbackStore.ts — the data-access seam for the feedback factory.
3
+ *
4
+ * The ported processor logic (fingerprint / similarity / cluster / verify /
5
+ * reopen) is pure; the *stateful* operations (read unprocessed items, read active
6
+ * clusters, create/merge/reopen clusters, mark processed, count) live behind this
7
+ * interface so the canonical instance's real adapter (Prisma cloud DB — the
8
+ * blocked, credentials-gated piece) is a thin shim, while everything is buildable
9
+ * and testable today against `InMemoryFeedbackStore`.
10
+ *
11
+ * This is the dependency-injection boundary called out in the migration spec:
12
+ * front → Vercel, processor → Instar job, DB → cloud (Prisma). The interface
13
+ * shape is driven by what the ported drivers + the composition (process.ts) need.
14
+ */
15
+ import type { FeedbackItem, Cluster, ClusterResult } from '../processor/types.js';
16
+ import type { ReopenDecision } from '../processor/reopen.js';
17
+ /** Read-only observability counters for the capture→cluster→reopen loop (spec §2.7). */
18
+ export interface FeedbackMetrics {
19
+ captured: number;
20
+ created: number;
21
+ merged: number;
22
+ reopened: number;
23
+ }
24
+ export interface FeedbackStore {
25
+ /** Unprocessed feedback, oldest first (mirrors the reference's `status:'unprocessed'` query). */
26
+ getUnprocessedFeedback(): FeedbackItem[];
27
+ /** Active clusters (status != 'resolved'), the merge candidates. */
28
+ getActiveClusters(): Cluster[];
29
+ getCluster(clusterId: string): Cluster | undefined;
30
+ /** Create a new cluster from an item (or bump reportCount if it already exists). */
31
+ upsertClusterFromItem(clusterId: string, item: FeedbackItem): void;
32
+ /** Merge an item into an existing cluster (bump reportCount). */
33
+ mergeIntoCluster(clusterId: string, item: FeedbackItem): void;
34
+ /** Apply an auto-reopen decision (status, recurrence bump, audit-note append). */
35
+ applyReopen(clusterId: string, decision: ReopenDecision): void;
36
+ /** Mark a feedback item processed + linked to its cluster. */
37
+ markProcessed(feedbackId: string, clusterId: string): void;
38
+ metrics(): FeedbackMetrics;
39
+ }
40
+ /**
41
+ * In-memory FeedbackStore — the test/reference implementation. Faithfully mirrors
42
+ * the reference's create/merge/reopen field mutations (reportCount increment,
43
+ * recurrenceCount bump on regression, status change, note append) so the
44
+ * composition can be integration-tested without a database.
45
+ */
46
+ export declare class InMemoryFeedbackStore implements FeedbackStore {
47
+ private feedback;
48
+ private clusters;
49
+ private counts;
50
+ constructor(seed?: {
51
+ feedback?: FeedbackItem[];
52
+ clusters?: Cluster[];
53
+ });
54
+ getUnprocessedFeedback(): FeedbackItem[];
55
+ getActiveClusters(): Cluster[];
56
+ getCluster(clusterId: string): Cluster | undefined;
57
+ upsertClusterFromItem(clusterId: string, item: FeedbackItem): void;
58
+ mergeIntoCluster(clusterId: string, _item: FeedbackItem): void;
59
+ applyReopen(clusterId: string, decision: ReopenDecision): void;
60
+ markProcessed(feedbackId: string, clusterId: string): void;
61
+ metrics(): FeedbackMetrics;
62
+ /** Test helper: snapshot all clusters. */
63
+ allClusters(): Cluster[];
64
+ }
65
+ export type { ClusterResult };
66
+ //# sourceMappingURL=FeedbackStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FeedbackStore.d.ts","sourceRoot":"","sources":["../../../src/feedback-factory/store/FeedbackStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAClF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE7D,wFAAwF;AACxF,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,iGAAiG;IACjG,sBAAsB,IAAI,YAAY,EAAE,CAAC;IACzC,oEAAoE;IACpE,iBAAiB,IAAI,OAAO,EAAE,CAAC;IAC/B,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;IACnD,oFAAoF;IACpF,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI,CAAC;IACnE,iEAAiE;IACjE,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI,CAAC;IAC9D,kFAAkF;IAClF,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;IAC/D,8DAA8D;IAC9D,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3D,OAAO,IAAI,eAAe,CAAC;CAC5B;AAED;;;;;GAKG;AACH,qBAAa,qBAAsB,YAAW,aAAa;IACzD,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,MAAM,CAAwE;gBAE1E,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAA;KAAE;IAKtE,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;IAYlE,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI;IAM9D,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,IAAI;IAW9D,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAM1D,OAAO,IAAI,eAAe;IAI1B,0CAA0C;IAC1C,WAAW,IAAI,OAAO,EAAE;CAGzB;AAED,YAAY,EAAE,aAAa,EAAE,CAAC"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * FeedbackStore.ts — the data-access seam for the feedback factory.
3
+ *
4
+ * The ported processor logic (fingerprint / similarity / cluster / verify /
5
+ * reopen) is pure; the *stateful* operations (read unprocessed items, read active
6
+ * clusters, create/merge/reopen clusters, mark processed, count) live behind this
7
+ * interface so the canonical instance's real adapter (Prisma cloud DB — the
8
+ * blocked, credentials-gated piece) is a thin shim, while everything is buildable
9
+ * and testable today against `InMemoryFeedbackStore`.
10
+ *
11
+ * This is the dependency-injection boundary called out in the migration spec:
12
+ * front → Vercel, processor → Instar job, DB → cloud (Prisma). The interface
13
+ * shape is driven by what the ported drivers + the composition (process.ts) need.
14
+ */
15
+ /**
16
+ * In-memory FeedbackStore — the test/reference implementation. Faithfully mirrors
17
+ * the reference's create/merge/reopen field mutations (reportCount increment,
18
+ * recurrenceCount bump on regression, status change, note append) so the
19
+ * composition can be integration-tested without a database.
20
+ */
21
+ export class InMemoryFeedbackStore {
22
+ feedback = new Map();
23
+ clusters = new Map();
24
+ counts = { captured: 0, created: 0, merged: 0, reopened: 0 };
25
+ constructor(seed) {
26
+ for (const f of seed?.feedback ?? [])
27
+ this.feedback.set(f.feedbackId, { status: 'unprocessed', ...f });
28
+ for (const c of seed?.clusters ?? [])
29
+ this.clusters.set(c.clusterId, { ...c });
30
+ }
31
+ getUnprocessedFeedback() {
32
+ return [...this.feedback.values()]
33
+ .filter((f) => (f.status ?? 'unprocessed') === 'unprocessed')
34
+ .sort((a, b) => String(a.receivedAt ?? '').localeCompare(String(b.receivedAt ?? '')));
35
+ }
36
+ getActiveClusters() {
37
+ return [...this.clusters.values()].filter((c) => c.status !== 'resolved');
38
+ }
39
+ getCluster(clusterId) {
40
+ return this.clusters.get(clusterId);
41
+ }
42
+ upsertClusterFromItem(clusterId, item) {
43
+ const existing = this.clusters.get(clusterId);
44
+ if (existing) {
45
+ existing.reportCount = (existing.reportCount ?? 0) + 1;
46
+ }
47
+ else {
48
+ this.clusters.set(clusterId, {
49
+ clusterId, title: item.title, description: item.description, type: item.type, reportCount: 1,
50
+ });
51
+ this.counts.created++;
52
+ }
53
+ }
54
+ mergeIntoCluster(clusterId, _item) {
55
+ const c = this.clusters.get(clusterId);
56
+ if (c)
57
+ c.reportCount = (c.reportCount ?? 0) + 1;
58
+ this.counts.merged++;
59
+ }
60
+ applyReopen(clusterId, decision) {
61
+ const c = this.clusters.get(clusterId);
62
+ if (!c)
63
+ return;
64
+ c.status = decision.newStatus;
65
+ if (decision.bumpRecurrence)
66
+ c.recurrenceCount = (c.recurrenceCount ?? 0) + 1;
67
+ const field = decision.annotateField;
68
+ const prior = c[field] ? `${c[field]}\n\n` : '';
69
+ c[field] = prior + decision.note;
70
+ this.counts.reopened++;
71
+ }
72
+ markProcessed(feedbackId, clusterId) {
73
+ const f = this.feedback.get(feedbackId);
74
+ if (f) {
75
+ f.status = 'processing';
76
+ f.clusterId = clusterId;
77
+ }
78
+ this.counts.captured++;
79
+ }
80
+ metrics() {
81
+ return { ...this.counts };
82
+ }
83
+ /** Test helper: snapshot all clusters. */
84
+ allClusters() {
85
+ return [...this.clusters.values()].map((c) => ({ ...c }));
86
+ }
87
+ }
88
+ //# sourceMappingURL=FeedbackStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FeedbackStore.js","sourceRoot":"","sources":["../../../src/feedback-factory/store/FeedbackStore.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.20",
3
+ "version": "1.3.21",
4
4
  "description": "Coherence infrastructure for self-evolving AI agents — on the Claude Code or Codex subscription you already have.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-05-27T07:58:06.900Z",
5
- "instarVersion": "1.3.20",
4
+ "generatedAt": "2026-05-27T08:23:24.754Z",
5
+ "instarVersion": "1.3.21",
6
6
  "entryCount": 192,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -0,0 +1,26 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- bump: patch -->
4
+
5
+ ## What Changed
6
+
7
+ Ninth increment of the **Feedback Factory Migration** (Dawn → Echo; spec `docs/specs/feedback-factory-migration.md`, approved) — the piece that assembles the ported brain. Adds the data-access seam (`FeedbackStore` interface + an in-memory implementation) at `src/feedback-factory/store/`, the processing composition `processUnprocessed` at `src/feedback-factory/processor/process.ts` (which wires the already-ported clustering + auto-reopen together, mirroring the reference's "decide then apply" steps), and the factory's own observability counters (captured / created / merged / reopened).
8
+
9
+ The real database adapter is a thin shim that implements the same interface — left for cutover since it needs cloud credentials and the hosting decision. **Nothing is wired into a route or job yet** — no behavioral change.
10
+
11
+ ## What to Tell Your User
12
+
13
+ - The individual pieces of the feedback brain now snap together into one working pipeline (read new reports → group them → reopen regressions → count what happened), proven end-to-end against an in-memory test database.
14
+ - It also keeps its own running tally — how many reports came in, how many new bug-piles were created, how many merged, how many reopened — the start of the factory's self-monitoring.
15
+
16
+ ## Summary of New Capabilities
17
+
18
+ | Capability | How to Use |
19
+ |-----------|-----------|
20
+ | FeedbackStore seam + in-memory store | `src/feedback-factory/store/FeedbackStore.ts` — the DB-adapter boundary (real adapter at cutover) |
21
+ | Processing composition | `processUnprocessed(store, now)` in `src/feedback-factory/processor/process.ts` — not yet wired |
22
+ | Observability counters | `store.metrics()` — captured / created / merged / reopened |
23
+
24
+ ## Evidence
25
+
26
+ - The decision logic inside (clustering, auto-reopen) is each already proven identical to Dawn's original in earlier increments. This increment adds the integration-level proof that they **compose** correctly: a Tier-2 test runs the whole pipeline over a real in-memory store and asserts new clusters are created, duplicates merge (order-dependent), the false-merge guard blocks a mid-similarity merge into a fixed bug, a genuine regression auto-reopens (status → investigating, recurrence bumped, audit note written), the counters tally, and empty input is a clean no-op.
@@ -0,0 +1,36 @@
1
+ # Side-Effects Review — store seam + processing composition + observability (Phase 1, increment 9)
2
+
3
+ **Slug:** `feedback-factory-store-compose`
4
+ **Date:** `2026-05-27`
5
+ **Author:** Echo (autonomous)
6
+ **Spec:** `docs/specs/feedback-factory-migration.md` (converged v2, approved by Justin 2026-05-26)
7
+ **Scope:** The data-access seam (`FeedbackStore` interface + `InMemoryFeedbackStore`), the processing composition (`processUnprocessed` = clusterItems + computeReopen over the store), and the observability counters. The REAL Prisma adapter + the HTTP/app placement remain the blocked, credentials/decision-gated pieces.
8
+
9
+ ## Summary of the change
10
+
11
+ Adds:
12
+ - `src/feedback-factory/store/FeedbackStore.ts` — the dependency-injection boundary (interface + `FeedbackMetrics` counters + `InMemoryFeedbackStore`).
13
+ - `src/feedback-factory/processor/process.ts` — `processUnprocessed(store, now)`, which composes the already-parity'd pieces, mirroring the reference's `cmd_cluster` (decide) + `cmd_apply_clusters` (write) run back-to-back: read unprocessed + active clusters → `clusterItems` decides → apply create/merge → on a "possible regression" merge, `computeReopen` + `applyReopen` → mark processed → update counters.
14
+ - Observability: the `FeedbackMetrics` surface (captured / created / merged / reopened) the store maintains (spec §2.7's "meter the loop", minimal form).
15
+
16
+ This is the glue that assembles the ported brain. The real Prisma adapter is a thin shim that implements the same interface; **nothing is wired into a route/job yet** — no behavioral change.
17
+
18
+ ## Equivalence / verification
19
+
20
+ The constituent decision logic (`clusterItems`, `computeReopen`) is each already parity-verified against the reference (increments 4 + 8). This increment adds the **Tier-2 integration test** proving they COMPOSE correctly over the data seam: new clusters created, duplicates merged (order-dependent), the 0.55 false-merge guard blocks a mid-similarity merge into a fixed cluster, a real regression auto-reopens (status → investigating, recurrence bump, audit note), counters tally, and empty input is a no-op. `InMemoryFeedbackStore`'s field mutations (reportCount increment, recurrence bump on regression, note append with blank-line separator) mirror the reference's prisma `update` shapes.
21
+
22
+ ## Seven-dimension review
23
+
24
+ 1. **Over/under-reach** — Pure logic + an in-memory store; not wired to any route/job. The interface is shaped by what the ported drivers + composition need (not speculative). `InMemoryFeedbackStore` mutates only its own Maps.
25
+ 2. **Level-of-abstraction fit** — `store/` is the data-access layer; `process.ts` is the composition; the real DB adapter (Prisma) implements the interface later. Correct seam — the blocked app/DB decisions sit cleanly behind it.
26
+ 3. **Signal vs Authority** — The composition applies the decisions the parity'd pieces produce; the evidence gate / curated lifecycle still hold terminal authority (unchanged). Counters are read-only observability.
27
+ 4. **Interactions** — First module to IMPORT the others (`cluster`, `reopen`, `types`) — but only in `process.ts`, which nothing else imports yet. No runtime path touched.
28
+ 5. **Rollback cost** — Trivial: delete the store + process + tests. Additive.
29
+ 6. **Migration parity** — N/A. New internal library code; no agent-installed file touched. (The real-adapter wiring + sender repoint are the separate Migration-Parity'd cutover steps, blocked.)
30
+ 7. **Failure modes** — (a) Composition mis-wires create vs merge vs reopen → the Tier-2 integration test covers all branches. (b) Order-dependence lost → integration test asserts fb-2 merges fb-1's fresh cluster. (c) Reopen not triggered on regression → asserted (status/recurrence/note). (d) Counter drift → asserted per-branch + empty-input no-op.
31
+
32
+ ## Tests
33
+
34
+ - Tier-1 unit (CI): `tests/unit/feedback-factory/store.test.ts` — 6 tests (read filters, create/merge/reopen mutations, aged vs regression recurrence).
35
+ - **Tier-2 integration (CI): `tests/integration/feedback-factory-process.test.ts` — 4 tests** composing the full pipeline over the real `InMemoryFeedbackStore` (the integration tier the pure-logic increments deferred until a store existed).
36
+ - E2E (feature-alive) attaches when the processor job + canonical front are wired — that's gated on the blocked app-placement + Prisma-adapter decisions. Reasoned, documented.