instar 1.3.18 → 1.3.20
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.
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +104 -6
- package/dist/commands/server.js.map +1 -1
- package/dist/core/FencedLease.d.ts +126 -0
- package/dist/core/FencedLease.d.ts.map +1 -0
- package/dist/core/FencedLease.js +189 -0
- package/dist/core/FencedLease.js.map +1 -0
- package/dist/core/GitLeaseStore.d.ts +46 -0
- package/dist/core/GitLeaseStore.d.ts.map +1 -0
- package/dist/core/GitLeaseStore.js +79 -0
- package/dist/core/GitLeaseStore.js.map +1 -0
- package/dist/core/GitSync.d.ts +19 -0
- package/dist/core/GitSync.d.ts.map +1 -1
- package/dist/core/GitSync.js +77 -0
- package/dist/core/GitSync.js.map +1 -1
- package/dist/core/HandoffSentinel.d.ts +80 -0
- package/dist/core/HandoffSentinel.d.ts.map +1 -0
- package/dist/core/HandoffSentinel.js +141 -0
- package/dist/core/HandoffSentinel.js.map +1 -0
- package/dist/core/LeaseCoordinator.d.ts +141 -0
- package/dist/core/LeaseCoordinator.d.ts.map +1 -0
- package/dist/core/LeaseCoordinator.js +226 -0
- package/dist/core/LeaseCoordinator.js.map +1 -0
- package/dist/core/LiveTailBuffer.d.ts +64 -0
- package/dist/core/LiveTailBuffer.d.ts.map +1 -0
- package/dist/core/LiveTailBuffer.js +122 -0
- package/dist/core/LiveTailBuffer.js.map +1 -0
- package/dist/core/MultiMachineCoordinator.d.ts +49 -0
- package/dist/core/MultiMachineCoordinator.d.ts.map +1 -1
- package/dist/core/MultiMachineCoordinator.js +136 -0
- package/dist/core/MultiMachineCoordinator.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +24 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/RegistrySyncDebouncer.d.ts +77 -0
- package/dist/core/RegistrySyncDebouncer.d.ts.map +1 -0
- package/dist/core/RegistrySyncDebouncer.js +131 -0
- package/dist/core/RegistrySyncDebouncer.js.map +1 -0
- package/dist/core/liveTailRedaction.d.ts +36 -0
- package/dist/core/liveTailRedaction.d.ts.map +1 -0
- package/dist/core/liveTailRedaction.js +57 -0
- package/dist/core/liveTailRedaction.js.map +1 -0
- package/dist/core/registryReplayGuard.d.ts +59 -0
- package/dist/core/registryReplayGuard.d.ts.map +1 -0
- package/dist/core/registryReplayGuard.js +86 -0
- package/dist/core/registryReplayGuard.js.map +1 -0
- package/dist/core/seamlessnessConfig.d.ts +79 -0
- package/dist/core/seamlessnessConfig.d.ts.map +1 -0
- package/dist/core/seamlessnessConfig.js +131 -0
- package/dist/core/seamlessnessConfig.js.map +1 -0
- package/dist/core/types.d.ts +117 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/wireRegistrySync.d.ts +32 -0
- package/dist/core/wireRegistrySync.d.ts.map +1 -0
- package/dist/core/wireRegistrySync.js +31 -0
- package/dist/core/wireRegistrySync.js.map +1 -0
- package/dist/feedback-factory/dispatch/dispatch.d.ts +47 -0
- package/dist/feedback-factory/dispatch/dispatch.d.ts.map +1 -0
- package/dist/feedback-factory/dispatch/dispatch.js +72 -0
- package/dist/feedback-factory/dispatch/dispatch.js.map +1 -0
- package/dist/feedback-factory/processor/reopen.d.ts +39 -0
- package/dist/feedback-factory/processor/reopen.d.ts.map +1 -0
- package/dist/feedback-factory/processor/reopen.js +41 -0
- package/dist/feedback-factory/processor/reopen.js.map +1 -0
- package/dist/messaging/FencedOutbox.d.ts +42 -0
- package/dist/messaging/FencedOutbox.d.ts.map +1 -0
- package/dist/messaging/FencedOutbox.js +60 -0
- package/dist/messaging/FencedOutbox.js.map +1 -0
- package/dist/messaging/MessageProcessingLedger.d.ts +105 -0
- package/dist/messaging/MessageProcessingLedger.d.ts.map +1 -0
- package/dist/messaging/MessageProcessingLedger.js +206 -0
- package/dist/messaging/MessageProcessingLedger.js.map +1 -0
- package/dist/messaging/TelegramAdapter.d.ts +18 -1
- package/dist/messaging/TelegramAdapter.d.ts.map +1 -1
- package/dist/messaging/TelegramAdapter.js +50 -0
- package/dist/messaging/TelegramAdapter.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +7 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +1 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/routes.d.ts +2 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +6 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +64 -64
- package/src/scaffold/templates.ts +7 -0
- package/upgrades/1.3.19.md +23 -0
- package/upgrades/1.3.20.md +23 -0
- package/upgrades/side-effects/PR-BODY-cross-machine-seamlessness.md +63 -0
- package/upgrades/side-effects/cross-machine-seamlessness-g1-lease.md +71 -0
- package/upgrades/side-effects/cross-machine-seamlessness-g2-foundation.md +75 -0
- package/upgrades/side-effects/cross-machine-seamlessness-g3-telegram-contract.md +36 -0
- package/upgrades/side-effects/cross-machine-seamlessness-g3a-ledger-handoff.md +56 -0
- package/upgrades/side-effects/cross-machine-seamlessness-g3bc-livetail-outbox.md +51 -0
- package/upgrades/side-effects/cross-machine-seamlessness-p4-awareness-observability.md +54 -0
- package/upgrades/side-effects/feedback-factory-dispatch.md +31 -0
- package/upgrades/side-effects/feedback-factory-reopen.md +31 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wireRegistrySync.js","sourceRoot":"","sources":["../../src/core/wireRegistrySync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAkBH;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAC9B,WAAmC,EACnC,IAAuB;IAEvB,MAAM,MAAM,GAAG,CAAC,IAAa,EAAE,EAAW,EAAE,EAAE,CAC5C,IAAI,CAAC,iBAAiB,CAAC,cAAc,MAAM,CAAC,IAAI,CAAC,KAAK,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IACtE,MAAM,OAAO,GAAG,CAAC,KAAc,EAAE,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,cAAc,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAE1F,WAAW,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IACrC,WAAW,CAAC,EAAE,CAAC,kBAAkB,EAAE,OAAO,CAAC,CAAC;IAE5C,OAAO;QACL,WAAW,EAAE,CAAC,YAAY,EAAE,kBAAkB,CAAC;QAC/C,MAAM;YACJ,WAAW,CAAC,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;YACtC,WAAW,CAAC,GAAG,CAAC,kBAAkB,EAAE,OAAO,CAAC,CAAC;QAC/C,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dispatch.ts — framework-agnostic port of the dispatch (guidance-out) logic.
|
|
3
|
+
*
|
|
4
|
+
* Ports the pure logic of `the-portal/pages/api/instar/dispatches/index.ts` out of
|
|
5
|
+
* the Next.js handler: the dispatch type/priority vocab, the semver comparison used
|
|
6
|
+
* for version-compat filtering (an agent only receives a dispatch whose
|
|
7
|
+
* [minVersion, maxVersion] window includes the agent's version), the list query
|
|
8
|
+
* filter (since/type/version), and the create-dedup title normalization. The HTTP
|
|
9
|
+
* wiring + storage are excluded (the app-placement decision); this is the reusable
|
|
10
|
+
* core. Reference is TypeScript, so equivalence is by faithful transcription +
|
|
11
|
+
* exhaustive both-sides-of-boundary tests (not a cross-runtime parity harness).
|
|
12
|
+
*/
|
|
13
|
+
export declare const DISPATCH_TYPES: readonly ["strategy", "behavioral", "lesson", "configuration", "security", "action"];
|
|
14
|
+
export type DispatchType = typeof DISPATCH_TYPES[number];
|
|
15
|
+
export declare const DISPATCH_PRIORITIES: readonly ["low", "normal", "high", "critical"];
|
|
16
|
+
export type DispatchPriority = typeof DISPATCH_PRIORITIES[number];
|
|
17
|
+
export declare const SEMVER_RE: RegExp;
|
|
18
|
+
export declare function isValidDispatchType(type: unknown): type is DispatchType;
|
|
19
|
+
export declare function isValidDispatchPriority(p: unknown): p is DispatchPriority;
|
|
20
|
+
/** Port of parseVersion: leading major.minor.patch → [n,n,n]; [0,0,0] if unparseable. */
|
|
21
|
+
export declare function parseVersion(v: string): [number, number, number];
|
|
22
|
+
/** Port of isVersionGte: version >= min (major.minor.patch, equal counts as gte). */
|
|
23
|
+
export declare function isVersionGte(version: string, min: string): boolean;
|
|
24
|
+
/** Port of isVersionLte: version <= max (equal counts as lte). */
|
|
25
|
+
export declare function isVersionLte(version: string, max: string): boolean;
|
|
26
|
+
export interface DispatchRecord {
|
|
27
|
+
dispatchId: string;
|
|
28
|
+
type: string;
|
|
29
|
+
title: string;
|
|
30
|
+
content?: string;
|
|
31
|
+
priority?: string;
|
|
32
|
+
minVersion?: string | null;
|
|
33
|
+
maxVersion?: string | null;
|
|
34
|
+
active?: boolean;
|
|
35
|
+
createdAt?: string;
|
|
36
|
+
[k: string]: unknown;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Port of handleList's version-compat filter: a dispatch is included only if the
|
|
40
|
+
* agent's version is within its [minVersion, maxVersion] window. Mirrors the
|
|
41
|
+
* reference — applied only when the agent sent a valid-semver version; otherwise
|
|
42
|
+
* all candidates pass (the caller gates on SEMVER_RE before calling).
|
|
43
|
+
*/
|
|
44
|
+
export declare function filterDispatchesForVersion(dispatches: DispatchRecord[], version: string): DispatchRecord[];
|
|
45
|
+
/** Port of the create-path title normalization used for dedup: trim + cap at 500 chars. */
|
|
46
|
+
export declare function normalizeDispatchTitle(title: string): string;
|
|
47
|
+
//# sourceMappingURL=dispatch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../../src/feedback-factory/dispatch/dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,eAAO,MAAM,cAAc,sFAAuF,CAAC;AACnH,MAAM,MAAM,YAAY,GAAG,OAAO,cAAc,CAAC,MAAM,CAAC,CAAC;AACzD,eAAO,MAAM,mBAAmB,gDAAiD,CAAC;AAClF,MAAM,MAAM,gBAAgB,GAAG,OAAO,mBAAmB,CAAC,MAAM,CAAC,CAAC;AAElE,eAAO,MAAM,SAAS,QAA8B,CAAC;AAErD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,YAAY,CAEvE;AACD,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,gBAAgB,CAEzE;AAED,yFAAyF;AACzF,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAIhE;AAED,qFAAqF;AACrF,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAQlE;AAED,kEAAkE;AAClE,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAQlE;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,cAAc,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,cAAc,EAAE,CAM1G;AAED,2FAA2F;AAC3F,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE5D"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dispatch.ts — framework-agnostic port of the dispatch (guidance-out) logic.
|
|
3
|
+
*
|
|
4
|
+
* Ports the pure logic of `the-portal/pages/api/instar/dispatches/index.ts` out of
|
|
5
|
+
* the Next.js handler: the dispatch type/priority vocab, the semver comparison used
|
|
6
|
+
* for version-compat filtering (an agent only receives a dispatch whose
|
|
7
|
+
* [minVersion, maxVersion] window includes the agent's version), the list query
|
|
8
|
+
* filter (since/type/version), and the create-dedup title normalization. The HTTP
|
|
9
|
+
* wiring + storage are excluded (the app-placement decision); this is the reusable
|
|
10
|
+
* core. Reference is TypeScript, so equivalence is by faithful transcription +
|
|
11
|
+
* exhaustive both-sides-of-boundary tests (not a cross-runtime parity harness).
|
|
12
|
+
*/
|
|
13
|
+
export const DISPATCH_TYPES = ['strategy', 'behavioral', 'lesson', 'configuration', 'security', 'action'];
|
|
14
|
+
export const DISPATCH_PRIORITIES = ['low', 'normal', 'high', 'critical'];
|
|
15
|
+
export const SEMVER_RE = /^\d+\.\d+\.\d+(-[\w.]+)?$/;
|
|
16
|
+
export function isValidDispatchType(type) {
|
|
17
|
+
return typeof type === 'string' && DISPATCH_TYPES.includes(type);
|
|
18
|
+
}
|
|
19
|
+
export function isValidDispatchPriority(p) {
|
|
20
|
+
return typeof p === 'string' && DISPATCH_PRIORITIES.includes(p);
|
|
21
|
+
}
|
|
22
|
+
/** Port of parseVersion: leading major.minor.patch → [n,n,n]; [0,0,0] if unparseable. */
|
|
23
|
+
export function parseVersion(v) {
|
|
24
|
+
const match = /^(\d+)\.(\d+)\.(\d+)/.exec(v);
|
|
25
|
+
if (!match)
|
|
26
|
+
return [0, 0, 0];
|
|
27
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
28
|
+
}
|
|
29
|
+
/** Port of isVersionGte: version >= min (major.minor.patch, equal counts as gte). */
|
|
30
|
+
export function isVersionGte(version, min) {
|
|
31
|
+
const v = parseVersion(version);
|
|
32
|
+
const m = parseVersion(min);
|
|
33
|
+
for (let i = 0; i < 3; i++) {
|
|
34
|
+
if (v[i] > m[i])
|
|
35
|
+
return true;
|
|
36
|
+
if (v[i] < m[i])
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
return true; // equal
|
|
40
|
+
}
|
|
41
|
+
/** Port of isVersionLte: version <= max (equal counts as lte). */
|
|
42
|
+
export function isVersionLte(version, max) {
|
|
43
|
+
const v = parseVersion(version);
|
|
44
|
+
const m = parseVersion(max);
|
|
45
|
+
for (let i = 0; i < 3; i++) {
|
|
46
|
+
if (v[i] < m[i])
|
|
47
|
+
return true;
|
|
48
|
+
if (v[i] > m[i])
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
return true; // equal
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Port of handleList's version-compat filter: a dispatch is included only if the
|
|
55
|
+
* agent's version is within its [minVersion, maxVersion] window. Mirrors the
|
|
56
|
+
* reference — applied only when the agent sent a valid-semver version; otherwise
|
|
57
|
+
* all candidates pass (the caller gates on SEMVER_RE before calling).
|
|
58
|
+
*/
|
|
59
|
+
export function filterDispatchesForVersion(dispatches, version) {
|
|
60
|
+
return dispatches.filter((d) => {
|
|
61
|
+
if (d.minVersion && !isVersionGte(version, d.minVersion))
|
|
62
|
+
return false;
|
|
63
|
+
if (d.maxVersion && !isVersionLte(version, d.maxVersion))
|
|
64
|
+
return false;
|
|
65
|
+
return true;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** Port of the create-path title normalization used for dedup: trim + cap at 500 chars. */
|
|
69
|
+
export function normalizeDispatchTitle(title) {
|
|
70
|
+
return title.trim().slice(0, 500);
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=dispatch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatch.js","sourceRoot":"","sources":["../../../src/feedback-factory/dispatch/dispatch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* reopen.ts — TS port of the auto-reopen-on-regression decision.
|
|
3
|
+
*
|
|
4
|
+
* Ports the decision embedded in cmd_apply_clusters (the-portal/.claude/scripts/
|
|
5
|
+
* feedback-processor.py, the regression branch ~:1543) out of the DB-write code
|
|
6
|
+
* into a pure function. When the clustering driver (cluster.ts) merges a new
|
|
7
|
+
* report into a cluster that was already fixed/resolved/deferred (a "possible
|
|
8
|
+
* regression" note), the cluster is auto-reopened. This computes the reopen
|
|
9
|
+
* outcome from the cluster's prior status:
|
|
10
|
+
*
|
|
11
|
+
* - deferred → AGED-REOPEN → status 'new', annotate actionTaken, no recurrence bump
|
|
12
|
+
* - otherwise → REGRESSION → status 'investigating', annotate researchNotes, bump recurrenceCount
|
|
13
|
+
*
|
|
14
|
+
* The DB writes (the prisma update with `{ increment: 1 }` etc.) stay in the
|
|
15
|
+
* adapter; this is the pure decision + the audit-note text. `now` is injected.
|
|
16
|
+
* Reference is interleaved with DB I/O, so equivalence is by faithful
|
|
17
|
+
* transcription + exhaustive unit tests (no clean isolated reference function to
|
|
18
|
+
* run a cross-runtime harness against).
|
|
19
|
+
*/
|
|
20
|
+
import type { Cluster } from './types.js';
|
|
21
|
+
export interface ReopenDecision {
|
|
22
|
+
/** New lifecycle status to set on the reopened cluster. */
|
|
23
|
+
newStatus: 'new' | 'investigating';
|
|
24
|
+
/** Whether to bump recurrenceCount (chronic-regression accounting; NOT for aged-reopen). */
|
|
25
|
+
bumpRecurrence: boolean;
|
|
26
|
+
/** Which cluster field the audit note is appended to. */
|
|
27
|
+
annotateField: 'actionTaken' | 'researchNotes';
|
|
28
|
+
/** The note tag. */
|
|
29
|
+
noteTag: 'AGED-REOPEN' | 'REGRESSION';
|
|
30
|
+
/** The audit note appended to the chosen field. */
|
|
31
|
+
note: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Compute the auto-reopen outcome for a cluster being merged into on a regression.
|
|
35
|
+
* `wasStatus` is the cluster's status before reopen; `feedbackId` is the incoming
|
|
36
|
+
* report; `now` is the injected ISO timestamp (reference uses new Date().toISOString()).
|
|
37
|
+
*/
|
|
38
|
+
export declare function computeReopen(cluster: Cluster, feedbackId: string, now: string): ReopenDecision;
|
|
39
|
+
//# sourceMappingURL=reopen.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reopen.d.ts","sourceRoot":"","sources":["../../../src/feedback-factory/processor/reopen.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,WAAW,cAAc;IAC7B,2DAA2D;IAC3D,SAAS,EAAE,KAAK,GAAG,eAAe,CAAC;IACnC,4FAA4F;IAC5F,cAAc,EAAE,OAAO,CAAC;IACxB,yDAAyD;IACzD,aAAa,EAAE,aAAa,GAAG,eAAe,CAAC;IAC/C,oBAAoB;IACpB,OAAO,EAAE,aAAa,GAAG,YAAY,CAAC;IACtC,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,cAAc,CAiB/F"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* reopen.ts — TS port of the auto-reopen-on-regression decision.
|
|
3
|
+
*
|
|
4
|
+
* Ports the decision embedded in cmd_apply_clusters (the-portal/.claude/scripts/
|
|
5
|
+
* feedback-processor.py, the regression branch ~:1543) out of the DB-write code
|
|
6
|
+
* into a pure function. When the clustering driver (cluster.ts) merges a new
|
|
7
|
+
* report into a cluster that was already fixed/resolved/deferred (a "possible
|
|
8
|
+
* regression" note), the cluster is auto-reopened. This computes the reopen
|
|
9
|
+
* outcome from the cluster's prior status:
|
|
10
|
+
*
|
|
11
|
+
* - deferred → AGED-REOPEN → status 'new', annotate actionTaken, no recurrence bump
|
|
12
|
+
* - otherwise → REGRESSION → status 'investigating', annotate researchNotes, bump recurrenceCount
|
|
13
|
+
*
|
|
14
|
+
* The DB writes (the prisma update with `{ increment: 1 }` etc.) stay in the
|
|
15
|
+
* adapter; this is the pure decision + the audit-note text. `now` is injected.
|
|
16
|
+
* Reference is interleaved with DB I/O, so equivalence is by faithful
|
|
17
|
+
* transcription + exhaustive unit tests (no clean isolated reference function to
|
|
18
|
+
* run a cross-runtime harness against).
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Compute the auto-reopen outcome for a cluster being merged into on a regression.
|
|
22
|
+
* `wasStatus` is the cluster's status before reopen; `feedbackId` is the incoming
|
|
23
|
+
* report; `now` is the injected ISO timestamp (reference uses new Date().toISOString()).
|
|
24
|
+
*/
|
|
25
|
+
export function computeReopen(cluster, feedbackId, now) {
|
|
26
|
+
const wasStatus = cluster.status ?? null;
|
|
27
|
+
const isAged = wasStatus === 'deferred';
|
|
28
|
+
const newStatus = isAged ? 'new' : 'investigating';
|
|
29
|
+
const noteTag = isAged ? 'AGED-REOPEN' : 'REGRESSION';
|
|
30
|
+
const fixedInVersion = cluster.fixedInVersion || 'n/a';
|
|
31
|
+
// Verbatim from the reference's regressionNote template.
|
|
32
|
+
const note = `[${noteTag} ${now}] New report matched cluster previously marked '${wasStatus}' (fixedInVersion=${fixedInVersion}). Auto-reopened to '${newStatus}' for review. Report: ${feedbackId}`;
|
|
33
|
+
return {
|
|
34
|
+
newStatus,
|
|
35
|
+
bumpRecurrence: !isAged,
|
|
36
|
+
annotateField: isAged ? 'actionTaken' : 'researchNotes',
|
|
37
|
+
noteTag,
|
|
38
|
+
note,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=reopen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reopen.js","sourceRoot":"","sources":["../../../src/feedback-factory/processor/reopen.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAiBH;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,OAAgB,EAAE,UAAkB,EAAE,GAAW;IAC7E,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC;IACzC,MAAM,MAAM,GAAG,SAAS,KAAK,UAAU,CAAC;IACxC,MAAM,SAAS,GAA4B,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,eAAe,CAAC;IAC5E,MAAM,OAAO,GAAiC,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,YAAY,CAAC;IACpF,MAAM,cAAc,GAAI,OAAO,CAAC,cAAyB,IAAI,KAAK,CAAC;IAEnE,yDAAyD;IACzD,MAAM,IAAI,GAAG,IAAI,OAAO,IAAI,GAAG,mDAAmD,SAAS,qBAAqB,cAAc,wBAAwB,SAAS,yBAAyB,UAAU,EAAE,CAAC;IAErM,OAAO;QACL,SAAS;QACT,cAAc,EAAE,CAAC,MAAM;QACvB,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,eAAe;QACvD,OAAO;QACP,IAAI;KACL,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FencedOutbox — fencing-token-gated outbound reply path (spec §8 G3a).
|
|
3
|
+
*
|
|
4
|
+
* "No duplicate replies" is enforced here STRUCTURALLY, not by assuming a single
|
|
5
|
+
* consumer. A reply is sent only while this machine holds the lease at the epoch
|
|
6
|
+
* the reply was stamped under; a fenced (stale-epoch) machine's in-flight reply
|
|
7
|
+
* is SUPPRESSED at the send path. The reply is committed to the message ledger
|
|
8
|
+
* with a deterministic idempotency key (hash(dedupeKey + replyIndex)) so any
|
|
9
|
+
* machine re-running the same event reproduces it identically — and the ledger
|
|
10
|
+
* recognizes an already-committed reply and refuses to re-send.
|
|
11
|
+
*
|
|
12
|
+
* On a channel with native outbound dedup the idempotency key makes a re-send a
|
|
13
|
+
* no-op; on a channel without it (Telegram), the dual-medium reply_committed
|
|
14
|
+
* marker (propagated by the sync layer) is what prevents a failover re-send.
|
|
15
|
+
*/
|
|
16
|
+
import { MessageProcessingLedger } from './MessageProcessingLedger.js';
|
|
17
|
+
export interface FencedOutboxDeps {
|
|
18
|
+
ledger: MessageProcessingLedger;
|
|
19
|
+
/** The current effective fencing epoch. */
|
|
20
|
+
currentEpoch: () => number;
|
|
21
|
+
/** Whether this machine structurally holds the lease right now. */
|
|
22
|
+
holdsLease: () => boolean;
|
|
23
|
+
logger?: (msg: string) => void;
|
|
24
|
+
}
|
|
25
|
+
export interface SendOutcome {
|
|
26
|
+
sent: boolean;
|
|
27
|
+
suppressed: boolean;
|
|
28
|
+
reason: 'sent' | 'already-replied' | 'fenced-no-lease' | 'fenced-stale-epoch' | 'send-failed';
|
|
29
|
+
idempotencyKey: string;
|
|
30
|
+
}
|
|
31
|
+
export declare class FencedOutbox {
|
|
32
|
+
private readonly d;
|
|
33
|
+
constructor(deps: FencedOutboxDeps);
|
|
34
|
+
private log;
|
|
35
|
+
/**
|
|
36
|
+
* Send a reply for `dedupeKey`, stamped under `stampedEpoch` (the epoch the
|
|
37
|
+
* turn began processing under). Returns the outcome. The actual platform send
|
|
38
|
+
* is `sendFn`; it is invoked ONLY after the fencing checks pass.
|
|
39
|
+
*/
|
|
40
|
+
send(dedupeKey: string, replyIndex: number, stampedEpoch: number, sendFn: () => Promise<void>): Promise<SendOutcome>;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=FencedOutbox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FencedOutbox.d.ts","sourceRoot":"","sources":["../../src/messaging/FencedOutbox.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,uBAAuB,EAA8B,MAAM,8BAA8B,CAAC;AAEnG,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,uBAAuB,CAAC;IAChC,2CAA2C;IAC3C,YAAY,EAAE,MAAM,MAAM,CAAC;IAC3B,mEAAmE;IACnE,UAAU,EAAE,MAAM,OAAO,CAAC;IAC1B,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,iBAAiB,GAAG,iBAAiB,GAAG,oBAAoB,GAAG,aAAa,CAAC;IAC9F,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAmB;gBAEzB,IAAI,EAAE,gBAAgB;IAIlC,OAAO,CAAC,GAAG;IAIX;;;;OAIG;IACG,IAAI,CACR,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAC1B,OAAO,CAAC,WAAW,CAAC;CAgCxB"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FencedOutbox — fencing-token-gated outbound reply path (spec §8 G3a).
|
|
3
|
+
*
|
|
4
|
+
* "No duplicate replies" is enforced here STRUCTURALLY, not by assuming a single
|
|
5
|
+
* consumer. A reply is sent only while this machine holds the lease at the epoch
|
|
6
|
+
* the reply was stamped under; a fenced (stale-epoch) machine's in-flight reply
|
|
7
|
+
* is SUPPRESSED at the send path. The reply is committed to the message ledger
|
|
8
|
+
* with a deterministic idempotency key (hash(dedupeKey + replyIndex)) so any
|
|
9
|
+
* machine re-running the same event reproduces it identically — and the ledger
|
|
10
|
+
* recognizes an already-committed reply and refuses to re-send.
|
|
11
|
+
*
|
|
12
|
+
* On a channel with native outbound dedup the idempotency key makes a re-send a
|
|
13
|
+
* no-op; on a channel without it (Telegram), the dual-medium reply_committed
|
|
14
|
+
* marker (propagated by the sync layer) is what prevents a failover re-send.
|
|
15
|
+
*/
|
|
16
|
+
import { computeReplyIdempotencyKey } from './MessageProcessingLedger.js';
|
|
17
|
+
export class FencedOutbox {
|
|
18
|
+
d;
|
|
19
|
+
constructor(deps) {
|
|
20
|
+
this.d = deps;
|
|
21
|
+
}
|
|
22
|
+
log(m) {
|
|
23
|
+
this.d.logger?.(`[outbox] ${m}`);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Send a reply for `dedupeKey`, stamped under `stampedEpoch` (the epoch the
|
|
27
|
+
* turn began processing under). Returns the outcome. The actual platform send
|
|
28
|
+
* is `sendFn`; it is invoked ONLY after the fencing checks pass.
|
|
29
|
+
*/
|
|
30
|
+
async send(dedupeKey, replyIndex, stampedEpoch, sendFn) {
|
|
31
|
+
const idempotencyKey = computeReplyIdempotencyKey(dedupeKey, replyIndex);
|
|
32
|
+
// Idempotency: if a reply was already committed (locally or via a remote
|
|
33
|
+
// dual-medium marker), do not send again.
|
|
34
|
+
if (this.d.ledger.isActedOn(dedupeKey)) {
|
|
35
|
+
this.log(`already replied to ${dedupeKey} — suppressing`);
|
|
36
|
+
return { sent: false, suppressed: true, reason: 'already-replied', idempotencyKey };
|
|
37
|
+
}
|
|
38
|
+
// Fencing: only the current lease holder, at the stamped epoch, may send.
|
|
39
|
+
if (!this.d.holdsLease()) {
|
|
40
|
+
this.log(`fenced (no lease) — suppressing reply to ${dedupeKey}`);
|
|
41
|
+
return { sent: false, suppressed: true, reason: 'fenced-no-lease', idempotencyKey };
|
|
42
|
+
}
|
|
43
|
+
const epoch = this.d.currentEpoch();
|
|
44
|
+
if (epoch !== stampedEpoch) {
|
|
45
|
+
this.log(`fenced (stale epoch ${stampedEpoch} != current ${epoch}) — suppressing reply to ${dedupeKey}`);
|
|
46
|
+
return { sent: false, suppressed: true, reason: 'fenced-stale-epoch', idempotencyKey };
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
await sendFn();
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
this.log(`send failed for ${dedupeKey}: ${err instanceof Error ? err.message : String(err)}`);
|
|
53
|
+
return { sent: false, suppressed: false, reason: 'send-failed', idempotencyKey };
|
|
54
|
+
}
|
|
55
|
+
// Commit the reply marker (durable, synchronous) BEFORE advancing the cursor.
|
|
56
|
+
this.d.ledger.commitReply(dedupeKey, idempotencyKey, stampedEpoch);
|
|
57
|
+
return { sent: true, suppressed: false, reason: 'sent', idempotencyKey };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=FencedOutbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FencedOutbox.js","sourceRoot":"","sources":["../../src/messaging/FencedOutbox.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAA2B,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAkBnG,MAAM,OAAO,YAAY;IACN,CAAC,CAAmB;IAErC,YAAY,IAAsB;QAChC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;IAChB,CAAC;IAEO,GAAG,CAAC,CAAS;QACnB,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;IACnC,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,IAAI,CACR,SAAiB,EACjB,UAAkB,EAClB,YAAoB,EACpB,MAA2B;QAE3B,MAAM,cAAc,GAAG,0BAA0B,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;QAEzE,yEAAyE;QACzE,0CAA0C;QAC1C,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC;YACvC,IAAI,CAAC,GAAG,CAAC,sBAAsB,SAAS,gBAAgB,CAAC,CAAC;YAC1D,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,iBAAiB,EAAE,cAAc,EAAE,CAAC;QACtF,CAAC;QAED,0EAA0E;QAC1E,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC;YACzB,IAAI,CAAC,GAAG,CAAC,4CAA4C,SAAS,EAAE,CAAC,CAAC;YAClE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,iBAAiB,EAAE,cAAc,EAAE,CAAC;QACtF,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,YAAY,EAAE,CAAC;QACpC,IAAI,KAAK,KAAK,YAAY,EAAE,CAAC;YAC3B,IAAI,CAAC,GAAG,CAAC,uBAAuB,YAAY,eAAe,KAAK,4BAA4B,SAAS,EAAE,CAAC,CAAC;YACzG,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,oBAAoB,EAAE,cAAc,EAAE,CAAC;QACzF,CAAC;QAED,IAAI,CAAC;YACH,MAAM,MAAM,EAAE,CAAC;QACjB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,GAAG,CAAC,mBAAmB,SAAS,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC9F,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,cAAc,EAAE,CAAC;QACnF,CAAC;QAED,8EAA8E;QAC9E,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,SAAS,EAAE,cAAc,EAAE,YAAY,CAAC,CAAC;QACnE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;IAC3E,CAAC;CACF"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MessageProcessingLedger — the no-loss / no-duplicate-reply guarantee (spec §8 G3a).
|
|
3
|
+
*
|
|
4
|
+
* A durable, SQLite-backed record of each inbound message's lifecycle:
|
|
5
|
+
* received → processing → reply_committed → cursor_advanced
|
|
6
|
+
* keyed by the adapter's `dedupeKey(rawEvent)` (Telegram update_id, Slack
|
|
7
|
+
* event_id/client_msg_id). Rules:
|
|
8
|
+
*
|
|
9
|
+
* - An event whose dedupeKey is already reply_committed/cursor_advanced is
|
|
10
|
+
* NEVER acted on again — a redelivery (Telegram retry, Slack reconnect, or a
|
|
11
|
+
* transfer-window overlap during a handoff) is recognized and dropped. This
|
|
12
|
+
* is what makes "no duplicate replies" structural, not merely "only one
|
|
13
|
+
* consumer".
|
|
14
|
+
* - The ingress cursor advances ONLY on durable completion (cursor_advanced),
|
|
15
|
+
* so a crash before completion replays the event (at-least-once) and the
|
|
16
|
+
* ledger makes the replay a no-op-or-resume (exactly-once effect).
|
|
17
|
+
* - Each transition is flushed to local SQLite synchronously on commit (WAL +
|
|
18
|
+
* NORMAL), so a same-machine crash-restart never double-acts.
|
|
19
|
+
* - A stuck `processing` entry past maxProcessingMs (old holder fenced
|
|
20
|
+
* mid-turn) is re-runnable by the current holder from its stored input.
|
|
21
|
+
*
|
|
22
|
+
* Substrate is SQLite (the proven PendingRelayStore/CommitmentTracker path) —
|
|
23
|
+
* NOT a new ad-hoc JSON file and NOT a git-synced blob. Schema self-initializes
|
|
24
|
+
* on first access (no PostUpdateMigrator step needed). Per-agent-id isolation.
|
|
25
|
+
*/
|
|
26
|
+
export type LedgerState = 'received' | 'processing' | 'reply_committed' | 'cursor_advanced';
|
|
27
|
+
export interface LedgerEntry {
|
|
28
|
+
dedupeKey: string;
|
|
29
|
+
platform: string;
|
|
30
|
+
topic: string | null;
|
|
31
|
+
state: LedgerState;
|
|
32
|
+
receivedAt: string;
|
|
33
|
+
processingStartedAt: string | null;
|
|
34
|
+
replyCommittedAt: string | null;
|
|
35
|
+
cursorAdvancedAt: string | null;
|
|
36
|
+
replyIdempotencyKey: string | null;
|
|
37
|
+
replyEpoch: number | null;
|
|
38
|
+
inputSnapshot: string | null;
|
|
39
|
+
attempts: number;
|
|
40
|
+
}
|
|
41
|
+
export declare function resolveMessageLedgerPath(stateDir: string, agentId: string): string;
|
|
42
|
+
/**
|
|
43
|
+
* Deterministic outbound idempotency key — any machine re-running the same
|
|
44
|
+
* inbound event reproduces it identically (spec §8 G3a). Used so a failover
|
|
45
|
+
* re-send is recognized as the same reply.
|
|
46
|
+
*/
|
|
47
|
+
export declare function computeReplyIdempotencyKey(dedupeKey: string, replyIndex: number): string;
|
|
48
|
+
export declare class MessageProcessingLedger {
|
|
49
|
+
private readonly db;
|
|
50
|
+
readonly path: string;
|
|
51
|
+
private constructor();
|
|
52
|
+
static open(agentId: string, stateDir: string): MessageProcessingLedger;
|
|
53
|
+
/** Open an in-memory ledger (tests). */
|
|
54
|
+
static openMemory(): MessageProcessingLedger;
|
|
55
|
+
/**
|
|
56
|
+
* Record an inbound event. Idempotent on dedupeKey (INSERT OR IGNORE).
|
|
57
|
+
* Returns whether this is the first time we've seen it + the current state.
|
|
58
|
+
* A caller seeing firstSeen:false with an acted-on state must DROP the event.
|
|
59
|
+
*/
|
|
60
|
+
record(dedupeKey: string, opts: {
|
|
61
|
+
platform: string;
|
|
62
|
+
topic?: string | null;
|
|
63
|
+
input?: string;
|
|
64
|
+
}): {
|
|
65
|
+
firstSeen: boolean;
|
|
66
|
+
state: LedgerState;
|
|
67
|
+
};
|
|
68
|
+
/** Has this event already been acted on (reply committed or cursor advanced)? */
|
|
69
|
+
isActedOn(dedupeKey: string): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Claim an event for processing under the given fencing epoch. Returns false
|
|
72
|
+
* if it has already been acted on (drop). A stuck 'processing' entry can be
|
|
73
|
+
* re-claimed (the old holder was fenced); attempts is incremented.
|
|
74
|
+
*/
|
|
75
|
+
beginProcessing(dedupeKey: string, epoch: number): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Commit that a reply was sent. Records the deterministic idempotency key and
|
|
78
|
+
* the fencing epoch it was committed under. Idempotent — committing twice is
|
|
79
|
+
* a no-op (the marker already exists).
|
|
80
|
+
*/
|
|
81
|
+
commitReply(dedupeKey: string, replyIdempotencyKey: string, epoch: number): void;
|
|
82
|
+
/** Advance the ingress cursor — only call after durable reply commit. */
|
|
83
|
+
advanceCursor(dedupeKey: string): void;
|
|
84
|
+
/**
|
|
85
|
+
* Apply a reply_committed marker propagated from another machine (dual-medium
|
|
86
|
+
* marker, spec §8 G3a): if we have the entry and it's not yet acted on, mark
|
|
87
|
+
* it committed so a failover does not re-send. Creates the entry if unknown
|
|
88
|
+
* (the other machine saw it first).
|
|
89
|
+
*/
|
|
90
|
+
applyRemoteReplyMarker(dedupeKey: string, opts: {
|
|
91
|
+
platform: string;
|
|
92
|
+
replyIdempotencyKey: string;
|
|
93
|
+
epoch: number;
|
|
94
|
+
topic?: string | null;
|
|
95
|
+
}): void;
|
|
96
|
+
/**
|
|
97
|
+
* Entries stuck in 'processing' past maxProcessingMs — eligible for re-run by
|
|
98
|
+
* the current lease holder from their stored input (spec §8 G3a). The old
|
|
99
|
+
* holder's abandoned output is discarded (the new holder re-executes).
|
|
100
|
+
*/
|
|
101
|
+
reclaimStuck(maxProcessingMs: number, nowMs?: number): LedgerEntry[];
|
|
102
|
+
get(dedupeKey: string): LedgerEntry | null;
|
|
103
|
+
close(): void;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=MessageProcessingLedger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MessageProcessingLedger.d.ts","sourceRoot":"","sources":["../../src/messaging/MessageProcessingLedger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAQH,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,YAAY,GAAG,iBAAiB,GAAG,iBAAiB,CAAC;AAE5F,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,EAAE,WAAW,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAoBD,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAGlF;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAExF;AAED,qBAAa,uBAAuB;IAClC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAuB;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,OAAO;IAKP,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,uBAAuB;IAYvE,wCAAwC;IACxC,MAAM,CAAC,UAAU,IAAI,uBAAuB;IAO5C;;;;OAIG;IACH,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG;QAC5F,SAAS,EAAE,OAAO,CAAC;QACnB,KAAK,EAAE,WAAW,CAAC;KACpB;IAYD,iFAAiF;IACjF,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAKrC;;;;OAIG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO;IAc1D;;;;OAIG;IACH,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAUhF,yEAAyE;IACzE,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAStC;;;;;OAKG;IACH,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,mBAAmB,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI;IAe9I;;;;OAIG;IACH,YAAY,CAAC,eAAe,EAAE,MAAM,EAAE,KAAK,GAAE,MAAmB,GAAG,WAAW,EAAE;IAahF,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAK1C,KAAK,IAAI,IAAI;CAGd"}
|