macp-sdk-typescript 0.7.0 → 0.8.0
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/CHANGELOG.md +18 -0
- package/dist/agent/transports.js +10 -1
- package/dist/agent/types.d.ts +12 -0
- package/dist/base-session.js +1 -1
- package/dist/decision.js +1 -1
- package/dist/handoff.js +1 -1
- package/dist/projections/base.d.ts +149 -0
- package/dist/projections/base.js +126 -0
- package/dist/projections/decision.d.ts +43 -0
- package/dist/projections/decision.js +75 -0
- package/dist/projections/handoff.d.ts +42 -0
- package/dist/projections/handoff.js +55 -0
- package/dist/projections/proposal.d.ts +42 -0
- package/dist/projections/proposal.js +55 -0
- package/dist/projections/quorum.d.ts +43 -0
- package/dist/projections/quorum.js +91 -5
- package/dist/projections/task.d.ts +42 -0
- package/dist/projections/task.js +55 -0
- package/dist/proposal.js +1 -1
- package/dist/quorum.js +1 -1
- package/dist/task.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/watchers.js +8 -0
- package/package.json +9 -9
|
@@ -2,14 +2,61 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.HandoffProjection = void 0;
|
|
4
4
|
const constants_1 = require("../constants");
|
|
5
|
+
const logging_1 = require("../logging");
|
|
5
6
|
class HandoffProjection {
|
|
6
7
|
handoffs = new Map();
|
|
8
|
+
/**
|
|
9
|
+
* The session's accepted history, one envelope per unique `message_id`. See
|
|
10
|
+
* `BaseProjection.transcript` (`src/projections/base.ts`) for the full
|
|
11
|
+
* redelivery-idempotence contract (RFC-MACP-0006 §3.2); duplicated here
|
|
12
|
+
* only as a one-line pointer.
|
|
13
|
+
*/
|
|
7
14
|
transcript = [];
|
|
15
|
+
/**
|
|
16
|
+
* Cardinality anomalies recorded while replaying this projection's accepted
|
|
17
|
+
* transcript. See `BaseProjection.anomalies` (`src/projections/base.ts`)
|
|
18
|
+
* for the canonical description; duplicated here only as a one-line
|
|
19
|
+
* pointer. No built-in detection populates this for Handoff mode.
|
|
20
|
+
*/
|
|
21
|
+
anomalies = [];
|
|
8
22
|
phase = 'Pending';
|
|
9
23
|
commitment;
|
|
24
|
+
/**
|
|
25
|
+
* `message_id`s already applied to this projection. See
|
|
26
|
+
* `BaseProjection.seenMessageIds` (`src/projections/base.ts`) for the full
|
|
27
|
+
* redelivery-idempotence rationale (RFC-MACP-0006 §3.2); duplicated here
|
|
28
|
+
* only as a one-line pointer.
|
|
29
|
+
*/
|
|
30
|
+
seenMessageIds = new Set();
|
|
31
|
+
/**
|
|
32
|
+
* Apply one envelope to this projection's in-process state.
|
|
33
|
+
*
|
|
34
|
+
* Input contract: **accepted-only**, caller-maintained (`Envelope` carries
|
|
35
|
+
* no acceptance marker). Canonical source: `schemas/conformance/README.md`
|
|
36
|
+
* "Notes:", RFC-MACP-0007 §5.3, RFC-MACP-0011 §5. Full rationale and failure
|
|
37
|
+
* mode are documented once, on `BaseProjection.applyEnvelope`
|
|
38
|
+
* (`src/projections/base.ts`), and duplicated here only as a one-line
|
|
39
|
+
* pointer so six independent copies of the same prose cannot drift.
|
|
40
|
+
*
|
|
41
|
+
* Redelivery idempotence: a redelivered envelope (same `message_id`) is a
|
|
42
|
+
* non-event — not appended to `transcript`, not passed to the `switch`.
|
|
43
|
+
* See `BaseProjection.applyEnvelope` for the full RFC-MACP-0006 §3.2
|
|
44
|
+
* citation; duplicated here only as a one-line pointer.
|
|
45
|
+
*/
|
|
10
46
|
applyEnvelope(envelope, protoRegistry) {
|
|
11
47
|
if (envelope.mode !== constants_1.MODE_HANDOFF)
|
|
12
48
|
return;
|
|
49
|
+
if (envelope.messageId) {
|
|
50
|
+
if (this.seenMessageIds.has(envelope.messageId)) {
|
|
51
|
+
logging_1.logger.debug('projection redelivery ignored', {
|
|
52
|
+
messageId: envelope.messageId,
|
|
53
|
+
mode: envelope.mode,
|
|
54
|
+
messageType: envelope.messageType,
|
|
55
|
+
});
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
this.seenMessageIds.add(envelope.messageId);
|
|
59
|
+
}
|
|
13
60
|
this.transcript.push(envelope);
|
|
14
61
|
const payload = protoRegistry.decodeKnownPayload(envelope.mode, envelope.messageType, envelope.payload);
|
|
15
62
|
switch (envelope.messageType) {
|
|
@@ -74,6 +121,14 @@ class HandoffProjection {
|
|
|
74
121
|
break;
|
|
75
122
|
}
|
|
76
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* True once at least one `ProjectionAnomaly` has been recorded. See
|
|
126
|
+
* `BaseProjection.hasAnomalies` (`src/projections/base.ts`) for the
|
|
127
|
+
* canonical description; duplicated here only as a one-line pointer.
|
|
128
|
+
*/
|
|
129
|
+
get hasAnomalies() {
|
|
130
|
+
return this.anomalies.length > 0;
|
|
131
|
+
}
|
|
77
132
|
get isCommitted() {
|
|
78
133
|
return this.commitment !== undefined;
|
|
79
134
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ProjectionAnomaly } from './base';
|
|
1
2
|
import type { Envelope } from '../types';
|
|
2
3
|
import type { ProtoRegistry } from '../proto-registry';
|
|
3
4
|
export interface ProposalRecord {
|
|
@@ -24,10 +25,51 @@ export declare class ProposalProjection {
|
|
|
24
25
|
readonly proposals: Map<string, ProposalRecord>;
|
|
25
26
|
readonly accepts: ProposalAcceptRecord[];
|
|
26
27
|
readonly rejections: ProposalRejectRecord[];
|
|
28
|
+
/**
|
|
29
|
+
* The session's accepted history, one envelope per unique `message_id`. See
|
|
30
|
+
* `BaseProjection.transcript` (`src/projections/base.ts`) for the full
|
|
31
|
+
* redelivery-idempotence contract (RFC-MACP-0006 §3.2); duplicated here
|
|
32
|
+
* only as a one-line pointer.
|
|
33
|
+
*/
|
|
27
34
|
readonly transcript: Envelope[];
|
|
35
|
+
/**
|
|
36
|
+
* Cardinality anomalies recorded while replaying this projection's accepted
|
|
37
|
+
* transcript. See `BaseProjection.anomalies` (`src/projections/base.ts`)
|
|
38
|
+
* for the canonical description; duplicated here only as a one-line
|
|
39
|
+
* pointer. No built-in detection populates this for Proposal mode.
|
|
40
|
+
*/
|
|
41
|
+
readonly anomalies: ProjectionAnomaly[];
|
|
28
42
|
phase: 'Negotiating' | 'TerminalRejected' | 'Committed';
|
|
29
43
|
commitment?: Record<string, unknown>;
|
|
44
|
+
/**
|
|
45
|
+
* `message_id`s already applied to this projection. See
|
|
46
|
+
* `BaseProjection.seenMessageIds` (`src/projections/base.ts`) for the full
|
|
47
|
+
* redelivery-idempotence rationale (RFC-MACP-0006 §3.2); duplicated here
|
|
48
|
+
* only as a one-line pointer.
|
|
49
|
+
*/
|
|
50
|
+
private readonly seenMessageIds;
|
|
51
|
+
/**
|
|
52
|
+
* Apply one envelope to this projection's in-process state.
|
|
53
|
+
*
|
|
54
|
+
* Input contract: **accepted-only**, caller-maintained (`Envelope` carries
|
|
55
|
+
* no acceptance marker). Canonical source: `schemas/conformance/README.md`
|
|
56
|
+
* "Notes:", RFC-MACP-0007 §5.3, RFC-MACP-0011 §5. Full rationale and failure
|
|
57
|
+
* mode are documented once, on `BaseProjection.applyEnvelope`
|
|
58
|
+
* (`src/projections/base.ts`), and duplicated here only as a one-line
|
|
59
|
+
* pointer so six independent copies of the same prose cannot drift.
|
|
60
|
+
*
|
|
61
|
+
* Redelivery idempotence: a redelivered envelope (same `message_id`) is a
|
|
62
|
+
* non-event — not appended to `transcript`, not passed to the `switch`.
|
|
63
|
+
* See `BaseProjection.applyEnvelope` for the full RFC-MACP-0006 §3.2
|
|
64
|
+
* citation; duplicated here only as a one-line pointer.
|
|
65
|
+
*/
|
|
30
66
|
applyEnvelope(envelope: Envelope, protoRegistry: ProtoRegistry): void;
|
|
67
|
+
/**
|
|
68
|
+
* True once at least one `ProjectionAnomaly` has been recorded. See
|
|
69
|
+
* `BaseProjection.hasAnomalies` (`src/projections/base.ts`) for the
|
|
70
|
+
* canonical description; duplicated here only as a one-line pointer.
|
|
71
|
+
*/
|
|
72
|
+
get hasAnomalies(): boolean;
|
|
31
73
|
get isCommitted(): boolean;
|
|
32
74
|
get isPositiveOutcome(): boolean | undefined;
|
|
33
75
|
activeProposals(): ProposalRecord[];
|
|
@@ -2,16 +2,63 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ProposalProjection = void 0;
|
|
4
4
|
const constants_1 = require("../constants");
|
|
5
|
+
const logging_1 = require("../logging");
|
|
5
6
|
class ProposalProjection {
|
|
6
7
|
proposals = new Map();
|
|
7
8
|
accepts = [];
|
|
8
9
|
rejections = [];
|
|
10
|
+
/**
|
|
11
|
+
* The session's accepted history, one envelope per unique `message_id`. See
|
|
12
|
+
* `BaseProjection.transcript` (`src/projections/base.ts`) for the full
|
|
13
|
+
* redelivery-idempotence contract (RFC-MACP-0006 §3.2); duplicated here
|
|
14
|
+
* only as a one-line pointer.
|
|
15
|
+
*/
|
|
9
16
|
transcript = [];
|
|
17
|
+
/**
|
|
18
|
+
* Cardinality anomalies recorded while replaying this projection's accepted
|
|
19
|
+
* transcript. See `BaseProjection.anomalies` (`src/projections/base.ts`)
|
|
20
|
+
* for the canonical description; duplicated here only as a one-line
|
|
21
|
+
* pointer. No built-in detection populates this for Proposal mode.
|
|
22
|
+
*/
|
|
23
|
+
anomalies = [];
|
|
10
24
|
phase = 'Negotiating';
|
|
11
25
|
commitment;
|
|
26
|
+
/**
|
|
27
|
+
* `message_id`s already applied to this projection. See
|
|
28
|
+
* `BaseProjection.seenMessageIds` (`src/projections/base.ts`) for the full
|
|
29
|
+
* redelivery-idempotence rationale (RFC-MACP-0006 §3.2); duplicated here
|
|
30
|
+
* only as a one-line pointer.
|
|
31
|
+
*/
|
|
32
|
+
seenMessageIds = new Set();
|
|
33
|
+
/**
|
|
34
|
+
* Apply one envelope to this projection's in-process state.
|
|
35
|
+
*
|
|
36
|
+
* Input contract: **accepted-only**, caller-maintained (`Envelope` carries
|
|
37
|
+
* no acceptance marker). Canonical source: `schemas/conformance/README.md`
|
|
38
|
+
* "Notes:", RFC-MACP-0007 §5.3, RFC-MACP-0011 §5. Full rationale and failure
|
|
39
|
+
* mode are documented once, on `BaseProjection.applyEnvelope`
|
|
40
|
+
* (`src/projections/base.ts`), and duplicated here only as a one-line
|
|
41
|
+
* pointer so six independent copies of the same prose cannot drift.
|
|
42
|
+
*
|
|
43
|
+
* Redelivery idempotence: a redelivered envelope (same `message_id`) is a
|
|
44
|
+
* non-event — not appended to `transcript`, not passed to the `switch`.
|
|
45
|
+
* See `BaseProjection.applyEnvelope` for the full RFC-MACP-0006 §3.2
|
|
46
|
+
* citation; duplicated here only as a one-line pointer.
|
|
47
|
+
*/
|
|
12
48
|
applyEnvelope(envelope, protoRegistry) {
|
|
13
49
|
if (envelope.mode !== constants_1.MODE_PROPOSAL)
|
|
14
50
|
return;
|
|
51
|
+
if (envelope.messageId) {
|
|
52
|
+
if (this.seenMessageIds.has(envelope.messageId)) {
|
|
53
|
+
logging_1.logger.debug('projection redelivery ignored', {
|
|
54
|
+
messageId: envelope.messageId,
|
|
55
|
+
mode: envelope.mode,
|
|
56
|
+
messageType: envelope.messageType,
|
|
57
|
+
});
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
this.seenMessageIds.add(envelope.messageId);
|
|
61
|
+
}
|
|
15
62
|
this.transcript.push(envelope);
|
|
16
63
|
const payload = protoRegistry.decodeKnownPayload(envelope.mode, envelope.messageType, envelope.payload);
|
|
17
64
|
switch (envelope.messageType) {
|
|
@@ -77,6 +124,14 @@ class ProposalProjection {
|
|
|
77
124
|
break;
|
|
78
125
|
}
|
|
79
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* True once at least one `ProjectionAnomaly` has been recorded. See
|
|
129
|
+
* `BaseProjection.hasAnomalies` (`src/projections/base.ts`) for the
|
|
130
|
+
* canonical description; duplicated here only as a one-line pointer.
|
|
131
|
+
*/
|
|
132
|
+
get hasAnomalies() {
|
|
133
|
+
return this.anomalies.length > 0;
|
|
134
|
+
}
|
|
80
135
|
get isCommitted() {
|
|
81
136
|
return this.commitment !== undefined;
|
|
82
137
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ProjectionAnomaly } from './base';
|
|
1
2
|
import type { Envelope } from '../types';
|
|
2
3
|
import type { ProtoRegistry } from '../proto-registry';
|
|
3
4
|
export interface ApprovalRequestRecord {
|
|
@@ -16,10 +17,52 @@ export interface BallotRecord {
|
|
|
16
17
|
export declare class QuorumProjection {
|
|
17
18
|
readonly requests: Map<string, ApprovalRequestRecord>;
|
|
18
19
|
readonly ballots: Map<string, Map<string, BallotRecord>>;
|
|
20
|
+
/**
|
|
21
|
+
* The session's accepted history, one envelope per unique `message_id`. See
|
|
22
|
+
* `BaseProjection.transcript` (`src/projections/base.ts`) for the full
|
|
23
|
+
* redelivery-idempotence contract (RFC-MACP-0006 §3.2); duplicated here
|
|
24
|
+
* only as a one-line pointer.
|
|
25
|
+
*/
|
|
19
26
|
readonly transcript: Envelope[];
|
|
27
|
+
/**
|
|
28
|
+
* Cardinality anomalies recorded while replaying this projection's accepted
|
|
29
|
+
* transcript (e.g. a duplicate ballot across `Approve`/`Reject`/`Abstain`
|
|
30
|
+
* from the same sender for the same `request_id`). See
|
|
31
|
+
* `BaseProjection.anomalies` (`src/projections/base.ts`) for the canonical
|
|
32
|
+
* description; duplicated here only as a one-line pointer.
|
|
33
|
+
*/
|
|
34
|
+
readonly anomalies: ProjectionAnomaly[];
|
|
20
35
|
phase: 'Pending' | 'Voting' | 'Committed';
|
|
21
36
|
commitment?: Record<string, unknown>;
|
|
37
|
+
/**
|
|
38
|
+
* `message_id`s already applied to this projection. See
|
|
39
|
+
* `BaseProjection.seenMessageIds` (`src/projections/base.ts`) for the full
|
|
40
|
+
* redelivery-idempotence rationale (RFC-MACP-0006 §3.2); duplicated here
|
|
41
|
+
* only as a one-line pointer.
|
|
42
|
+
*/
|
|
43
|
+
private readonly seenMessageIds;
|
|
44
|
+
/**
|
|
45
|
+
* Apply one envelope to this projection's in-process state.
|
|
46
|
+
*
|
|
47
|
+
* Input contract: **accepted-only**, caller-maintained (`Envelope` carries
|
|
48
|
+
* no acceptance marker). Canonical source: `schemas/conformance/README.md`
|
|
49
|
+
* "Notes:", RFC-MACP-0007 §5.3, RFC-MACP-0011 §5. Full rationale and failure
|
|
50
|
+
* mode are documented once, on `BaseProjection.applyEnvelope`
|
|
51
|
+
* (`src/projections/base.ts`), and duplicated here only as a one-line
|
|
52
|
+
* pointer so six independent copies of the same prose cannot drift.
|
|
53
|
+
*
|
|
54
|
+
* Redelivery idempotence: a redelivered envelope (same `message_id`) is a
|
|
55
|
+
* non-event — not appended to `transcript`, not passed to the `switch`.
|
|
56
|
+
* See `BaseProjection.applyEnvelope` for the full RFC-MACP-0006 §3.2
|
|
57
|
+
* citation; duplicated here only as a one-line pointer.
|
|
58
|
+
*/
|
|
22
59
|
applyEnvelope(envelope: Envelope, protoRegistry: ProtoRegistry): void;
|
|
60
|
+
/**
|
|
61
|
+
* True once at least one `ProjectionAnomaly` has been recorded. See
|
|
62
|
+
* `BaseProjection.hasAnomalies` (`src/projections/base.ts`) for the
|
|
63
|
+
* canonical description; duplicated here only as a one-line pointer.
|
|
64
|
+
*/
|
|
65
|
+
get hasAnomalies(): boolean;
|
|
23
66
|
get isCommitted(): boolean;
|
|
24
67
|
get isPositiveOutcome(): boolean | undefined;
|
|
25
68
|
private setBallot;
|
|
@@ -2,15 +2,63 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.QuorumProjection = void 0;
|
|
4
4
|
const constants_1 = require("../constants");
|
|
5
|
+
const logging_1 = require("../logging");
|
|
5
6
|
class QuorumProjection {
|
|
6
7
|
requests = new Map();
|
|
7
8
|
ballots = new Map();
|
|
9
|
+
/**
|
|
10
|
+
* The session's accepted history, one envelope per unique `message_id`. See
|
|
11
|
+
* `BaseProjection.transcript` (`src/projections/base.ts`) for the full
|
|
12
|
+
* redelivery-idempotence contract (RFC-MACP-0006 §3.2); duplicated here
|
|
13
|
+
* only as a one-line pointer.
|
|
14
|
+
*/
|
|
8
15
|
transcript = [];
|
|
16
|
+
/**
|
|
17
|
+
* Cardinality anomalies recorded while replaying this projection's accepted
|
|
18
|
+
* transcript (e.g. a duplicate ballot across `Approve`/`Reject`/`Abstain`
|
|
19
|
+
* from the same sender for the same `request_id`). See
|
|
20
|
+
* `BaseProjection.anomalies` (`src/projections/base.ts`) for the canonical
|
|
21
|
+
* description; duplicated here only as a one-line pointer.
|
|
22
|
+
*/
|
|
23
|
+
anomalies = [];
|
|
9
24
|
phase = 'Pending';
|
|
10
25
|
commitment;
|
|
26
|
+
/**
|
|
27
|
+
* `message_id`s already applied to this projection. See
|
|
28
|
+
* `BaseProjection.seenMessageIds` (`src/projections/base.ts`) for the full
|
|
29
|
+
* redelivery-idempotence rationale (RFC-MACP-0006 §3.2); duplicated here
|
|
30
|
+
* only as a one-line pointer.
|
|
31
|
+
*/
|
|
32
|
+
seenMessageIds = new Set();
|
|
33
|
+
/**
|
|
34
|
+
* Apply one envelope to this projection's in-process state.
|
|
35
|
+
*
|
|
36
|
+
* Input contract: **accepted-only**, caller-maintained (`Envelope` carries
|
|
37
|
+
* no acceptance marker). Canonical source: `schemas/conformance/README.md`
|
|
38
|
+
* "Notes:", RFC-MACP-0007 §5.3, RFC-MACP-0011 §5. Full rationale and failure
|
|
39
|
+
* mode are documented once, on `BaseProjection.applyEnvelope`
|
|
40
|
+
* (`src/projections/base.ts`), and duplicated here only as a one-line
|
|
41
|
+
* pointer so six independent copies of the same prose cannot drift.
|
|
42
|
+
*
|
|
43
|
+
* Redelivery idempotence: a redelivered envelope (same `message_id`) is a
|
|
44
|
+
* non-event — not appended to `transcript`, not passed to the `switch`.
|
|
45
|
+
* See `BaseProjection.applyEnvelope` for the full RFC-MACP-0006 §3.2
|
|
46
|
+
* citation; duplicated here only as a one-line pointer.
|
|
47
|
+
*/
|
|
11
48
|
applyEnvelope(envelope, protoRegistry) {
|
|
12
49
|
if (envelope.mode !== constants_1.MODE_QUORUM)
|
|
13
50
|
return;
|
|
51
|
+
if (envelope.messageId) {
|
|
52
|
+
if (this.seenMessageIds.has(envelope.messageId)) {
|
|
53
|
+
logging_1.logger.debug('projection redelivery ignored', {
|
|
54
|
+
messageId: envelope.messageId,
|
|
55
|
+
mode: envelope.mode,
|
|
56
|
+
messageType: envelope.messageType,
|
|
57
|
+
});
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
this.seenMessageIds.add(envelope.messageId);
|
|
61
|
+
}
|
|
14
62
|
this.transcript.push(envelope);
|
|
15
63
|
const payload = protoRegistry.decodeKnownPayload(envelope.mode, envelope.messageType, envelope.payload);
|
|
16
64
|
switch (envelope.messageType) {
|
|
@@ -22,17 +70,17 @@ class QuorumProjection {
|
|
|
22
70
|
}
|
|
23
71
|
case 'Approve': {
|
|
24
72
|
const record = payload;
|
|
25
|
-
this.setBallot(record.requestId,
|
|
73
|
+
this.setBallot(envelope, record.requestId, 'approve', record.reason);
|
|
26
74
|
break;
|
|
27
75
|
}
|
|
28
76
|
case 'Reject': {
|
|
29
77
|
const record = payload;
|
|
30
|
-
this.setBallot(record.requestId,
|
|
78
|
+
this.setBallot(envelope, record.requestId, 'reject', record.reason);
|
|
31
79
|
break;
|
|
32
80
|
}
|
|
33
81
|
case 'Abstain': {
|
|
34
82
|
const record = payload;
|
|
35
|
-
this.setBallot(record.requestId,
|
|
83
|
+
this.setBallot(envelope, record.requestId, 'abstain', record.reason);
|
|
36
84
|
break;
|
|
37
85
|
}
|
|
38
86
|
case 'Commitment': {
|
|
@@ -44,6 +92,14 @@ class QuorumProjection {
|
|
|
44
92
|
break;
|
|
45
93
|
}
|
|
46
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* True once at least one `ProjectionAnomaly` has been recorded. See
|
|
97
|
+
* `BaseProjection.hasAnomalies` (`src/projections/base.ts`) for the
|
|
98
|
+
* canonical description; duplicated here only as a one-line pointer.
|
|
99
|
+
*/
|
|
100
|
+
get hasAnomalies() {
|
|
101
|
+
return this.anomalies.length > 0;
|
|
102
|
+
}
|
|
47
103
|
get isCommitted() {
|
|
48
104
|
return this.commitment !== undefined;
|
|
49
105
|
}
|
|
@@ -54,9 +110,39 @@ class QuorumProjection {
|
|
|
54
110
|
this.commitment.outcome_positive;
|
|
55
111
|
return val !== undefined ? Boolean(val) : true;
|
|
56
112
|
}
|
|
57
|
-
setBallot(
|
|
113
|
+
setBallot(envelope, requestId, vote, reason) {
|
|
58
114
|
const senderMap = this.ballots.get(requestId) ?? new Map();
|
|
59
|
-
senderMap.
|
|
115
|
+
const kept = senderMap.get(envelope.sender);
|
|
116
|
+
if (kept !== undefined) {
|
|
117
|
+
// RFC-MACP-0011 §5 rule 3 (`:67`): participation is MAY (a participant need
|
|
118
|
+
// not cast a ballot), but the cap of one ballot per participant ACROSS
|
|
119
|
+
// Approve, Reject, and Abstain is enforced under §5's opening sentence,
|
|
120
|
+
// "Implementations MUST enforce the following:" (`:63`). Do NOT phrase this
|
|
121
|
+
// as "a participant MUST cast at most one ballot" — RFC-0011 puts the MUST
|
|
122
|
+
// on the implementation, not the participant (contrast RFC-MACP-0007 §5.3,
|
|
123
|
+
// which puts it directly on the participant; same obligation, different
|
|
124
|
+
// addressee). A later ballot of a *different* type is still a duplicate,
|
|
125
|
+
// not a change of vote, so this guard is keyed on the sender ALONE within
|
|
126
|
+
// the request — never on sender + vote. macp-runtime enforces this
|
|
127
|
+
// identically in all three arms (quorum.rs:164/184/204).
|
|
128
|
+
// Keeping the sender's FIRST ballot (rather than the last) is not stated
|
|
129
|
+
// by RFC-0011 either; it is parity with RFC-MACP-0007 §5 item 3's
|
|
130
|
+
// explicit first-stands rule for `Vote`, plus macp-runtime's enforced
|
|
131
|
+
// first-wins behaviour.
|
|
132
|
+
const anomaly = {
|
|
133
|
+
kind: 'duplicate_ballot',
|
|
134
|
+
mode: envelope.mode,
|
|
135
|
+
messageType: envelope.messageType,
|
|
136
|
+
messageId: envelope.messageId,
|
|
137
|
+
sender: envelope.sender,
|
|
138
|
+
subjectId: requestId,
|
|
139
|
+
detail: `sender ${envelope.sender} already cast '${kept.vote}' on request ${requestId}; discarded '${vote}'`,
|
|
140
|
+
};
|
|
141
|
+
this.anomalies.push(anomaly);
|
|
142
|
+
logging_1.logger.warn('projection anomaly', anomaly);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
senderMap.set(envelope.sender, { requestId, vote, reason, sender: envelope.sender });
|
|
60
146
|
this.ballots.set(requestId, senderMap);
|
|
61
147
|
}
|
|
62
148
|
approvalCount(requestId) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ProjectionAnomaly } from './base';
|
|
1
2
|
import type { Envelope } from '../types';
|
|
2
3
|
import type { ProtoRegistry } from '../proto-registry';
|
|
3
4
|
export interface TaskRecord {
|
|
@@ -37,10 +38,51 @@ export declare class TaskProjection {
|
|
|
37
38
|
readonly updates: TaskUpdateRecord[];
|
|
38
39
|
readonly completions: TaskCompletionRecord[];
|
|
39
40
|
readonly failures: TaskFailureRecord[];
|
|
41
|
+
/**
|
|
42
|
+
* The session's accepted history, one envelope per unique `message_id`. See
|
|
43
|
+
* `BaseProjection.transcript` (`src/projections/base.ts`) for the full
|
|
44
|
+
* redelivery-idempotence contract (RFC-MACP-0006 §3.2); duplicated here
|
|
45
|
+
* only as a one-line pointer.
|
|
46
|
+
*/
|
|
40
47
|
readonly transcript: Envelope[];
|
|
48
|
+
/**
|
|
49
|
+
* Cardinality anomalies recorded while replaying this projection's accepted
|
|
50
|
+
* transcript. See `BaseProjection.anomalies` (`src/projections/base.ts`)
|
|
51
|
+
* for the canonical description; duplicated here only as a one-line
|
|
52
|
+
* pointer. No built-in detection populates this for Task mode.
|
|
53
|
+
*/
|
|
54
|
+
readonly anomalies: ProjectionAnomaly[];
|
|
41
55
|
phase: 'Pending' | 'Requested' | 'InProgress' | 'Completed' | 'Failed' | 'Committed';
|
|
42
56
|
commitment?: Record<string, unknown>;
|
|
57
|
+
/**
|
|
58
|
+
* `message_id`s already applied to this projection. See
|
|
59
|
+
* `BaseProjection.seenMessageIds` (`src/projections/base.ts`) for the full
|
|
60
|
+
* redelivery-idempotence rationale (RFC-MACP-0006 §3.2); duplicated here
|
|
61
|
+
* only as a one-line pointer.
|
|
62
|
+
*/
|
|
63
|
+
private readonly seenMessageIds;
|
|
64
|
+
/**
|
|
65
|
+
* Apply one envelope to this projection's in-process state.
|
|
66
|
+
*
|
|
67
|
+
* Input contract: **accepted-only**, caller-maintained (`Envelope` carries
|
|
68
|
+
* no acceptance marker). Canonical source: `schemas/conformance/README.md`
|
|
69
|
+
* "Notes:", RFC-MACP-0007 §5.3, RFC-MACP-0011 §5. Full rationale and failure
|
|
70
|
+
* mode are documented once, on `BaseProjection.applyEnvelope`
|
|
71
|
+
* (`src/projections/base.ts`), and duplicated here only as a one-line
|
|
72
|
+
* pointer so six independent copies of the same prose cannot drift.
|
|
73
|
+
*
|
|
74
|
+
* Redelivery idempotence: a redelivered envelope (same `message_id`) is a
|
|
75
|
+
* non-event — not appended to `transcript`, not passed to the `switch`.
|
|
76
|
+
* See `BaseProjection.applyEnvelope` for the full RFC-MACP-0006 §3.2
|
|
77
|
+
* citation; duplicated here only as a one-line pointer.
|
|
78
|
+
*/
|
|
43
79
|
applyEnvelope(envelope: Envelope, protoRegistry: ProtoRegistry): void;
|
|
80
|
+
/**
|
|
81
|
+
* True once at least one `ProjectionAnomaly` has been recorded. See
|
|
82
|
+
* `BaseProjection.hasAnomalies` (`src/projections/base.ts`) for the
|
|
83
|
+
* canonical description; duplicated here only as a one-line pointer.
|
|
84
|
+
*/
|
|
85
|
+
get hasAnomalies(): boolean;
|
|
44
86
|
get isCommitted(): boolean;
|
|
45
87
|
get isPositiveOutcome(): boolean | undefined;
|
|
46
88
|
getTask(taskId: string): TaskRecord | undefined;
|
package/dist/projections/task.js
CHANGED
|
@@ -2,17 +2,64 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.TaskProjection = void 0;
|
|
4
4
|
const constants_1 = require("../constants");
|
|
5
|
+
const logging_1 = require("../logging");
|
|
5
6
|
class TaskProjection {
|
|
6
7
|
tasks = new Map();
|
|
7
8
|
updates = [];
|
|
8
9
|
completions = [];
|
|
9
10
|
failures = [];
|
|
11
|
+
/**
|
|
12
|
+
* The session's accepted history, one envelope per unique `message_id`. See
|
|
13
|
+
* `BaseProjection.transcript` (`src/projections/base.ts`) for the full
|
|
14
|
+
* redelivery-idempotence contract (RFC-MACP-0006 §3.2); duplicated here
|
|
15
|
+
* only as a one-line pointer.
|
|
16
|
+
*/
|
|
10
17
|
transcript = [];
|
|
18
|
+
/**
|
|
19
|
+
* Cardinality anomalies recorded while replaying this projection's accepted
|
|
20
|
+
* transcript. See `BaseProjection.anomalies` (`src/projections/base.ts`)
|
|
21
|
+
* for the canonical description; duplicated here only as a one-line
|
|
22
|
+
* pointer. No built-in detection populates this for Task mode.
|
|
23
|
+
*/
|
|
24
|
+
anomalies = [];
|
|
11
25
|
phase = 'Pending';
|
|
12
26
|
commitment;
|
|
27
|
+
/**
|
|
28
|
+
* `message_id`s already applied to this projection. See
|
|
29
|
+
* `BaseProjection.seenMessageIds` (`src/projections/base.ts`) for the full
|
|
30
|
+
* redelivery-idempotence rationale (RFC-MACP-0006 §3.2); duplicated here
|
|
31
|
+
* only as a one-line pointer.
|
|
32
|
+
*/
|
|
33
|
+
seenMessageIds = new Set();
|
|
34
|
+
/**
|
|
35
|
+
* Apply one envelope to this projection's in-process state.
|
|
36
|
+
*
|
|
37
|
+
* Input contract: **accepted-only**, caller-maintained (`Envelope` carries
|
|
38
|
+
* no acceptance marker). Canonical source: `schemas/conformance/README.md`
|
|
39
|
+
* "Notes:", RFC-MACP-0007 §5.3, RFC-MACP-0011 §5. Full rationale and failure
|
|
40
|
+
* mode are documented once, on `BaseProjection.applyEnvelope`
|
|
41
|
+
* (`src/projections/base.ts`), and duplicated here only as a one-line
|
|
42
|
+
* pointer so six independent copies of the same prose cannot drift.
|
|
43
|
+
*
|
|
44
|
+
* Redelivery idempotence: a redelivered envelope (same `message_id`) is a
|
|
45
|
+
* non-event — not appended to `transcript`, not passed to the `switch`.
|
|
46
|
+
* See `BaseProjection.applyEnvelope` for the full RFC-MACP-0006 §3.2
|
|
47
|
+
* citation; duplicated here only as a one-line pointer.
|
|
48
|
+
*/
|
|
13
49
|
applyEnvelope(envelope, protoRegistry) {
|
|
14
50
|
if (envelope.mode !== constants_1.MODE_TASK)
|
|
15
51
|
return;
|
|
52
|
+
if (envelope.messageId) {
|
|
53
|
+
if (this.seenMessageIds.has(envelope.messageId)) {
|
|
54
|
+
logging_1.logger.debug('projection redelivery ignored', {
|
|
55
|
+
messageId: envelope.messageId,
|
|
56
|
+
mode: envelope.mode,
|
|
57
|
+
messageType: envelope.messageType,
|
|
58
|
+
});
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
this.seenMessageIds.add(envelope.messageId);
|
|
62
|
+
}
|
|
16
63
|
this.transcript.push(envelope);
|
|
17
64
|
const payload = protoRegistry.decodeKnownPayload(envelope.mode, envelope.messageType, envelope.payload);
|
|
18
65
|
switch (envelope.messageType) {
|
|
@@ -87,6 +134,14 @@ class TaskProjection {
|
|
|
87
134
|
break;
|
|
88
135
|
}
|
|
89
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* True once at least one `ProjectionAnomaly` has been recorded. See
|
|
139
|
+
* `BaseProjection.hasAnomalies` (`src/projections/base.ts`) for the
|
|
140
|
+
* canonical description; duplicated here only as a one-line pointer.
|
|
141
|
+
*/
|
|
142
|
+
get hasAnomalies() {
|
|
143
|
+
return this.anomalies.length > 0;
|
|
144
|
+
}
|
|
90
145
|
get isCommitted() {
|
|
91
146
|
return this.commitment !== undefined;
|
|
92
147
|
}
|
package/dist/proposal.js
CHANGED
|
@@ -16,7 +16,7 @@ class ProposalSession {
|
|
|
16
16
|
projection = new proposal_1.ProposalProjection();
|
|
17
17
|
constructor(client, options = {}) {
|
|
18
18
|
this.client = client;
|
|
19
|
-
if (options.sessionId)
|
|
19
|
+
if (options.sessionId !== undefined)
|
|
20
20
|
(0, validation_1.validateSessionId)(options.sessionId);
|
|
21
21
|
this.sessionId = options.sessionId ?? (0, envelope_1.newSessionId)();
|
|
22
22
|
this.modeVersion = options.modeVersion ?? constants_1.DEFAULT_MODE_VERSION;
|
package/dist/quorum.js
CHANGED
|
@@ -16,7 +16,7 @@ class QuorumSession {
|
|
|
16
16
|
projection = new quorum_1.QuorumProjection();
|
|
17
17
|
constructor(client, options = {}) {
|
|
18
18
|
this.client = client;
|
|
19
|
-
if (options.sessionId)
|
|
19
|
+
if (options.sessionId !== undefined)
|
|
20
20
|
(0, validation_1.validateSessionId)(options.sessionId);
|
|
21
21
|
this.sessionId = options.sessionId ?? (0, envelope_1.newSessionId)();
|
|
22
22
|
this.modeVersion = options.modeVersion ?? constants_1.DEFAULT_MODE_VERSION;
|
package/dist/task.js
CHANGED
|
@@ -16,7 +16,7 @@ class TaskSession {
|
|
|
16
16
|
projection = new task_1.TaskProjection();
|
|
17
17
|
constructor(client, options = {}) {
|
|
18
18
|
this.client = client;
|
|
19
|
-
if (options.sessionId)
|
|
19
|
+
if (options.sessionId !== undefined)
|
|
20
20
|
(0, validation_1.validateSessionId)(options.sessionId);
|
|
21
21
|
this.sessionId = options.sessionId ?? (0, envelope_1.newSessionId)();
|
|
22
22
|
this.modeVersion = options.modeVersion ?? constants_1.DEFAULT_MODE_VERSION;
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
package/dist/watchers.js
CHANGED
|
@@ -96,6 +96,14 @@ function serverStreamToAsyncGenerator(stream) {
|
|
|
96
96
|
stream.cancel();
|
|
97
97
|
throw err;
|
|
98
98
|
},
|
|
99
|
+
// Required since TypeScript 6 / @types/node 26, which make `AsyncGenerator`
|
|
100
|
+
// extend `AsyncDisposable` (lib.esnext.disposable). Native `async function*`
|
|
101
|
+
// generators get this for free; this hand-rolled one has to declare it. It
|
|
102
|
+
// also makes every watcher stream usable with `await using`, which cancels
|
|
103
|
+
// the underlying gRPC stream on scope exit exactly like `return()` does.
|
|
104
|
+
async [Symbol.asyncDispose]() {
|
|
105
|
+
stream.cancel();
|
|
106
|
+
},
|
|
99
107
|
};
|
|
100
108
|
}
|
|
101
109
|
class ModeRegistryWatcher {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "macp-sdk-typescript",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "TypeScript SDK for the MACP Rust runtime",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -55,18 +55,18 @@
|
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@grpc/grpc-js": "^1.14.3",
|
|
57
57
|
"@grpc/proto-loader": "^0.8.0",
|
|
58
|
-
"@multiagentcoordinationprotocol/proto": "^0.1.
|
|
58
|
+
"@multiagentcoordinationprotocol/proto": "^0.1.9",
|
|
59
59
|
"protobufjs": "^8.6.5"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@types/node": "^
|
|
63
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
64
|
-
"@typescript-eslint/parser": "^8.
|
|
65
|
-
"@vitest/coverage-v8": "^
|
|
66
|
-
"eslint": "^9.
|
|
62
|
+
"@types/node": "^26.4.0",
|
|
63
|
+
"@typescript-eslint/eslint-plugin": "^8.68.0",
|
|
64
|
+
"@typescript-eslint/parser": "^8.68.0",
|
|
65
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
66
|
+
"eslint": "^10.9.1",
|
|
67
67
|
"prettier": "^3.0.0",
|
|
68
68
|
"tsx": "^4.21.0",
|
|
69
|
-
"typescript": "^
|
|
70
|
-
"vitest": "^
|
|
69
|
+
"typescript": "^6.0.3",
|
|
70
|
+
"vitest": "^4.1.11"
|
|
71
71
|
}
|
|
72
72
|
}
|