macp-sdk-typescript 0.10.0 → 0.11.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 +94 -0
- package/README.md +1 -1
- package/dist/policy.d.ts +54 -7
- package/dist/policy.js +143 -19
- package/dist/projections/base.js +24 -5
- package/dist/projections/decision.d.ts +5 -49
- package/dist/projections/decision.js +7 -82
- package/dist/projections/handoff.d.ts +5 -48
- package/dist/projections/handoff.js +5 -77
- package/dist/projections/proposal.d.ts +5 -48
- package/dist/projections/proposal.js +5 -77
- package/dist/projections/quorum.d.ts +5 -49
- package/dist/projections/quorum.js +7 -82
- package/dist/projections/task.d.ts +5 -48
- package/dist/projections/task.js +5 -77
- package/dist/proto-registry.d.ts +19 -5
- package/dist/proto-registry.js +24 -16
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { BaseProjection } from './base';
|
|
2
2
|
import type { Envelope } from '../types';
|
|
3
3
|
import type { ProtoRegistry } from '../proto-registry';
|
|
4
4
|
export interface HandoffRecord {
|
|
@@ -18,55 +18,12 @@ export interface HandoffRecord {
|
|
|
18
18
|
*/
|
|
19
19
|
implicit?: boolean;
|
|
20
20
|
}
|
|
21
|
-
export declare class HandoffProjection {
|
|
21
|
+
export declare class HandoffProjection extends BaseProjection {
|
|
22
|
+
protected readonly mode = "macp.mode.handoff.v1";
|
|
22
23
|
readonly handoffs: Map<string, HandoffRecord>;
|
|
23
|
-
/**
|
|
24
|
-
* The session's accepted history, one envelope per unique `message_id`. See
|
|
25
|
-
* `BaseProjection.transcript` (`src/projections/base.ts`) for the full
|
|
26
|
-
* redelivery-idempotence contract (RFC-MACP-0006 §3.2); duplicated here
|
|
27
|
-
* only as a one-line pointer.
|
|
28
|
-
*/
|
|
29
|
-
readonly transcript: Envelope[];
|
|
30
|
-
/**
|
|
31
|
-
* Cardinality anomalies recorded while replaying this projection's accepted
|
|
32
|
-
* transcript. See `BaseProjection.anomalies` (`src/projections/base.ts`)
|
|
33
|
-
* for the canonical description; duplicated here only as a one-line
|
|
34
|
-
* pointer. No built-in detection populates this for Handoff mode.
|
|
35
|
-
*/
|
|
36
|
-
readonly anomalies: ProjectionAnomaly[];
|
|
37
24
|
phase: 'Pending' | 'OfferPending' | 'ContextSharing' | 'Accepted' | 'Declined' | 'Committed';
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
* `message_id`s already applied to this projection. See
|
|
41
|
-
* `BaseProjection.seenMessageIds` (`src/projections/base.ts`) for the full
|
|
42
|
-
* redelivery-idempotence rationale (RFC-MACP-0006 §3.2); duplicated here
|
|
43
|
-
* only as a one-line pointer.
|
|
44
|
-
*/
|
|
45
|
-
private readonly seenMessageIds;
|
|
46
|
-
/**
|
|
47
|
-
* Apply one envelope to this projection's in-process state.
|
|
48
|
-
*
|
|
49
|
-
* Input contract: **accepted-only**, caller-maintained (`Envelope` carries
|
|
50
|
-
* no acceptance marker). Canonical source: `schemas/conformance/README.md`
|
|
51
|
-
* "Notes:", RFC-MACP-0007 §5.3, RFC-MACP-0011 §5. Full rationale and failure
|
|
52
|
-
* mode are documented once, on `BaseProjection.applyEnvelope`
|
|
53
|
-
* (`src/projections/base.ts`), and duplicated here only as a one-line
|
|
54
|
-
* pointer so six independent copies of the same prose cannot drift.
|
|
55
|
-
*
|
|
56
|
-
* Redelivery idempotence: a redelivered envelope (same `message_id`) is a
|
|
57
|
-
* non-event — not appended to `transcript`, not passed to the `switch`.
|
|
58
|
-
* See `BaseProjection.applyEnvelope` for the full RFC-MACP-0006 §3.2
|
|
59
|
-
* citation; duplicated here only as a one-line pointer.
|
|
60
|
-
*/
|
|
61
|
-
applyEnvelope(envelope: Envelope, protoRegistry: ProtoRegistry): void;
|
|
62
|
-
/**
|
|
63
|
-
* True once at least one `ProjectionAnomaly` has been recorded. See
|
|
64
|
-
* `BaseProjection.hasAnomalies` (`src/projections/base.ts`) for the
|
|
65
|
-
* canonical description; duplicated here only as a one-line pointer.
|
|
66
|
-
*/
|
|
67
|
-
get hasAnomalies(): boolean;
|
|
68
|
-
get isCommitted(): boolean;
|
|
69
|
-
get isPositiveOutcome(): boolean | undefined;
|
|
25
|
+
/** Handle a Handoff-mode envelope. See `BaseProjection.applyEnvelope` for the accepted-only input contract and redelivery/rollback semantics shared by all built-in modes. */
|
|
26
|
+
protected applyMode(envelope: Envelope, protoRegistry: ProtoRegistry): void;
|
|
70
27
|
getHandoff(handoffId: string): HandoffRecord | undefined;
|
|
71
28
|
isAccepted(handoffId: string): boolean;
|
|
72
29
|
isDeclined(handoffId: string): boolean;
|
|
@@ -2,62 +2,13 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.HandoffProjection = void 0;
|
|
4
4
|
const constants_1 = require("../constants");
|
|
5
|
-
const
|
|
6
|
-
class HandoffProjection {
|
|
5
|
+
const base_1 = require("./base");
|
|
6
|
+
class HandoffProjection extends base_1.BaseProjection {
|
|
7
|
+
mode = constants_1.MODE_HANDOFF;
|
|
7
8
|
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
|
-
*/
|
|
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 = [];
|
|
22
9
|
phase = 'Pending';
|
|
23
|
-
|
|
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
|
-
*/
|
|
46
|
-
applyEnvelope(envelope, protoRegistry) {
|
|
47
|
-
if (envelope.mode !== constants_1.MODE_HANDOFF)
|
|
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
|
-
}
|
|
60
|
-
this.transcript.push(envelope);
|
|
10
|
+
/** Handle a Handoff-mode envelope. See `BaseProjection.applyEnvelope` for the accepted-only input contract and redelivery/rollback semantics shared by all built-in modes. */
|
|
11
|
+
applyMode(envelope, protoRegistry) {
|
|
61
12
|
const payload = protoRegistry.decodeKnownPayload(envelope.mode, envelope.messageType, envelope.payload);
|
|
62
13
|
switch (envelope.messageType) {
|
|
63
14
|
case 'HandoffOffer': {
|
|
@@ -148,33 +99,10 @@ class HandoffProjection {
|
|
|
148
99
|
}
|
|
149
100
|
break;
|
|
150
101
|
}
|
|
151
|
-
case 'Commitment': {
|
|
152
|
-
this.commitment = payload;
|
|
153
|
-
this.phase = 'Committed';
|
|
154
|
-
break;
|
|
155
|
-
}
|
|
156
102
|
default:
|
|
157
103
|
break;
|
|
158
104
|
}
|
|
159
105
|
}
|
|
160
|
-
/**
|
|
161
|
-
* True once at least one `ProjectionAnomaly` has been recorded. See
|
|
162
|
-
* `BaseProjection.hasAnomalies` (`src/projections/base.ts`) for the
|
|
163
|
-
* canonical description; duplicated here only as a one-line pointer.
|
|
164
|
-
*/
|
|
165
|
-
get hasAnomalies() {
|
|
166
|
-
return this.anomalies.length > 0;
|
|
167
|
-
}
|
|
168
|
-
get isCommitted() {
|
|
169
|
-
return this.commitment !== undefined;
|
|
170
|
-
}
|
|
171
|
-
get isPositiveOutcome() {
|
|
172
|
-
if (!this.commitment)
|
|
173
|
-
return undefined;
|
|
174
|
-
const val = this.commitment.outcomePositive ??
|
|
175
|
-
this.commitment.outcome_positive;
|
|
176
|
-
return val !== undefined ? Boolean(val) : true;
|
|
177
|
-
}
|
|
178
106
|
getHandoff(handoffId) {
|
|
179
107
|
return this.handoffs.get(handoffId);
|
|
180
108
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { BaseProjection } from './base';
|
|
2
2
|
import type { Envelope } from '../types';
|
|
3
3
|
import type { ProtoRegistry } from '../proto-registry';
|
|
4
4
|
export interface ProposalRecord {
|
|
@@ -21,7 +21,8 @@ export interface ProposalRejectRecord {
|
|
|
21
21
|
reason?: string;
|
|
22
22
|
sender: string;
|
|
23
23
|
}
|
|
24
|
-
export declare class ProposalProjection {
|
|
24
|
+
export declare class ProposalProjection extends BaseProjection {
|
|
25
|
+
protected readonly mode = "macp.mode.proposal.v1";
|
|
25
26
|
readonly proposals: Map<string, ProposalRecord>;
|
|
26
27
|
readonly accepts: ProposalAcceptRecord[];
|
|
27
28
|
readonly rejections: ProposalRejectRecord[];
|
|
@@ -39,53 +40,9 @@ export declare class ProposalProjection {
|
|
|
39
40
|
* acceptance set, and the same commitment eligibility."
|
|
40
41
|
*/
|
|
41
42
|
private readonly latestAcceptBySender;
|
|
42
|
-
/**
|
|
43
|
-
* The session's accepted history, one envelope per unique `message_id`. See
|
|
44
|
-
* `BaseProjection.transcript` (`src/projections/base.ts`) for the full
|
|
45
|
-
* redelivery-idempotence contract (RFC-MACP-0006 §3.2); duplicated here
|
|
46
|
-
* only as a one-line pointer.
|
|
47
|
-
*/
|
|
48
|
-
readonly transcript: Envelope[];
|
|
49
|
-
/**
|
|
50
|
-
* Cardinality anomalies recorded while replaying this projection's accepted
|
|
51
|
-
* transcript. See `BaseProjection.anomalies` (`src/projections/base.ts`)
|
|
52
|
-
* for the canonical description; duplicated here only as a one-line
|
|
53
|
-
* pointer. No built-in detection populates this for Proposal mode.
|
|
54
|
-
*/
|
|
55
|
-
readonly anomalies: ProjectionAnomaly[];
|
|
56
43
|
phase: 'Negotiating' | 'TerminalRejected' | 'Committed';
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
* `message_id`s already applied to this projection. See
|
|
60
|
-
* `BaseProjection.seenMessageIds` (`src/projections/base.ts`) for the full
|
|
61
|
-
* redelivery-idempotence rationale (RFC-MACP-0006 §3.2); duplicated here
|
|
62
|
-
* only as a one-line pointer.
|
|
63
|
-
*/
|
|
64
|
-
private readonly seenMessageIds;
|
|
65
|
-
/**
|
|
66
|
-
* Apply one envelope to this projection's in-process state.
|
|
67
|
-
*
|
|
68
|
-
* Input contract: **accepted-only**, caller-maintained (`Envelope` carries
|
|
69
|
-
* no acceptance marker). Canonical source: `schemas/conformance/README.md`
|
|
70
|
-
* "Notes:", RFC-MACP-0007 §5.3, RFC-MACP-0011 §5. Full rationale and failure
|
|
71
|
-
* mode are documented once, on `BaseProjection.applyEnvelope`
|
|
72
|
-
* (`src/projections/base.ts`), and duplicated here only as a one-line
|
|
73
|
-
* pointer so six independent copies of the same prose cannot drift.
|
|
74
|
-
*
|
|
75
|
-
* Redelivery idempotence: a redelivered envelope (same `message_id`) is a
|
|
76
|
-
* non-event — not appended to `transcript`, not passed to the `switch`.
|
|
77
|
-
* See `BaseProjection.applyEnvelope` for the full RFC-MACP-0006 §3.2
|
|
78
|
-
* citation; duplicated here only as a one-line pointer.
|
|
79
|
-
*/
|
|
80
|
-
applyEnvelope(envelope: Envelope, protoRegistry: ProtoRegistry): void;
|
|
81
|
-
/**
|
|
82
|
-
* True once at least one `ProjectionAnomaly` has been recorded. See
|
|
83
|
-
* `BaseProjection.hasAnomalies` (`src/projections/base.ts`) for the
|
|
84
|
-
* canonical description; duplicated here only as a one-line pointer.
|
|
85
|
-
*/
|
|
86
|
-
get hasAnomalies(): boolean;
|
|
87
|
-
get isCommitted(): boolean;
|
|
88
|
-
get isPositiveOutcome(): boolean | undefined;
|
|
44
|
+
/** Handle a Proposal-mode envelope. See `BaseProjection.applyEnvelope` for the accepted-only input contract and redelivery/rollback semantics shared by all built-in modes. */
|
|
45
|
+
protected applyMode(envelope: Envelope, protoRegistry: ProtoRegistry): void;
|
|
89
46
|
activeProposals(): ProposalRecord[];
|
|
90
47
|
latestProposal(): ProposalRecord | undefined;
|
|
91
48
|
/**
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ProposalProjection = void 0;
|
|
4
4
|
const constants_1 = require("../constants");
|
|
5
|
-
const
|
|
6
|
-
class ProposalProjection {
|
|
5
|
+
const base_1 = require("./base");
|
|
6
|
+
class ProposalProjection extends base_1.BaseProjection {
|
|
7
|
+
mode = constants_1.MODE_PROPOSAL;
|
|
7
8
|
proposals = new Map();
|
|
8
9
|
accepts = [];
|
|
9
10
|
rejections = [];
|
|
@@ -21,59 +22,9 @@ class ProposalProjection {
|
|
|
21
22
|
* acceptance set, and the same commitment eligibility."
|
|
22
23
|
*/
|
|
23
24
|
latestAcceptBySender = new Map();
|
|
24
|
-
/**
|
|
25
|
-
* The session's accepted history, one envelope per unique `message_id`. See
|
|
26
|
-
* `BaseProjection.transcript` (`src/projections/base.ts`) for the full
|
|
27
|
-
* redelivery-idempotence contract (RFC-MACP-0006 §3.2); duplicated here
|
|
28
|
-
* only as a one-line pointer.
|
|
29
|
-
*/
|
|
30
|
-
transcript = [];
|
|
31
|
-
/**
|
|
32
|
-
* Cardinality anomalies recorded while replaying this projection's accepted
|
|
33
|
-
* transcript. See `BaseProjection.anomalies` (`src/projections/base.ts`)
|
|
34
|
-
* for the canonical description; duplicated here only as a one-line
|
|
35
|
-
* pointer. No built-in detection populates this for Proposal mode.
|
|
36
|
-
*/
|
|
37
|
-
anomalies = [];
|
|
38
25
|
phase = 'Negotiating';
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
* `message_id`s already applied to this projection. See
|
|
42
|
-
* `BaseProjection.seenMessageIds` (`src/projections/base.ts`) for the full
|
|
43
|
-
* redelivery-idempotence rationale (RFC-MACP-0006 §3.2); duplicated here
|
|
44
|
-
* only as a one-line pointer.
|
|
45
|
-
*/
|
|
46
|
-
seenMessageIds = new Set();
|
|
47
|
-
/**
|
|
48
|
-
* Apply one envelope to this projection's in-process state.
|
|
49
|
-
*
|
|
50
|
-
* Input contract: **accepted-only**, caller-maintained (`Envelope` carries
|
|
51
|
-
* no acceptance marker). Canonical source: `schemas/conformance/README.md`
|
|
52
|
-
* "Notes:", RFC-MACP-0007 §5.3, RFC-MACP-0011 §5. Full rationale and failure
|
|
53
|
-
* mode are documented once, on `BaseProjection.applyEnvelope`
|
|
54
|
-
* (`src/projections/base.ts`), and duplicated here only as a one-line
|
|
55
|
-
* pointer so six independent copies of the same prose cannot drift.
|
|
56
|
-
*
|
|
57
|
-
* Redelivery idempotence: a redelivered envelope (same `message_id`) is a
|
|
58
|
-
* non-event — not appended to `transcript`, not passed to the `switch`.
|
|
59
|
-
* See `BaseProjection.applyEnvelope` for the full RFC-MACP-0006 §3.2
|
|
60
|
-
* citation; duplicated here only as a one-line pointer.
|
|
61
|
-
*/
|
|
62
|
-
applyEnvelope(envelope, protoRegistry) {
|
|
63
|
-
if (envelope.mode !== constants_1.MODE_PROPOSAL)
|
|
64
|
-
return;
|
|
65
|
-
if (envelope.messageId) {
|
|
66
|
-
if (this.seenMessageIds.has(envelope.messageId)) {
|
|
67
|
-
logging_1.logger.debug('projection redelivery ignored', {
|
|
68
|
-
messageId: envelope.messageId,
|
|
69
|
-
mode: envelope.mode,
|
|
70
|
-
messageType: envelope.messageType,
|
|
71
|
-
});
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
this.seenMessageIds.add(envelope.messageId);
|
|
75
|
-
}
|
|
76
|
-
this.transcript.push(envelope);
|
|
26
|
+
/** Handle a Proposal-mode envelope. See `BaseProjection.applyEnvelope` for the accepted-only input contract and redelivery/rollback semantics shared by all built-in modes. */
|
|
27
|
+
applyMode(envelope, protoRegistry) {
|
|
77
28
|
const payload = protoRegistry.decodeKnownPayload(envelope.mode, envelope.messageType, envelope.payload);
|
|
78
29
|
switch (envelope.messageType) {
|
|
79
30
|
case 'Proposal': {
|
|
@@ -133,33 +84,10 @@ class ProposalProjection {
|
|
|
133
84
|
proposal.status = 'withdrawn';
|
|
134
85
|
break;
|
|
135
86
|
}
|
|
136
|
-
case 'Commitment': {
|
|
137
|
-
this.commitment = payload;
|
|
138
|
-
this.phase = 'Committed';
|
|
139
|
-
break;
|
|
140
|
-
}
|
|
141
87
|
default:
|
|
142
88
|
break;
|
|
143
89
|
}
|
|
144
90
|
}
|
|
145
|
-
/**
|
|
146
|
-
* True once at least one `ProjectionAnomaly` has been recorded. See
|
|
147
|
-
* `BaseProjection.hasAnomalies` (`src/projections/base.ts`) for the
|
|
148
|
-
* canonical description; duplicated here only as a one-line pointer.
|
|
149
|
-
*/
|
|
150
|
-
get hasAnomalies() {
|
|
151
|
-
return this.anomalies.length > 0;
|
|
152
|
-
}
|
|
153
|
-
get isCommitted() {
|
|
154
|
-
return this.commitment !== undefined;
|
|
155
|
-
}
|
|
156
|
-
get isPositiveOutcome() {
|
|
157
|
-
if (!this.commitment)
|
|
158
|
-
return undefined;
|
|
159
|
-
const val = this.commitment.outcomePositive ??
|
|
160
|
-
this.commitment.outcome_positive;
|
|
161
|
-
return val !== undefined ? Boolean(val) : true;
|
|
162
|
-
}
|
|
163
91
|
activeProposals() {
|
|
164
92
|
return [...this.proposals.values()].filter((p) => p.status === 'open');
|
|
165
93
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { BaseProjection } from './base';
|
|
2
2
|
import type { Envelope } from '../types';
|
|
3
3
|
import type { ProtoRegistry } from '../proto-registry';
|
|
4
4
|
export interface ApprovalRequestRecord {
|
|
@@ -14,57 +14,13 @@ export interface BallotRecord {
|
|
|
14
14
|
reason?: string;
|
|
15
15
|
sender: string;
|
|
16
16
|
}
|
|
17
|
-
export declare class QuorumProjection {
|
|
17
|
+
export declare class QuorumProjection extends BaseProjection {
|
|
18
|
+
protected readonly mode = "macp.mode.quorum.v1";
|
|
18
19
|
readonly requests: Map<string, ApprovalRequestRecord>;
|
|
19
20
|
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
|
-
*/
|
|
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[];
|
|
35
21
|
phase: 'Pending' | 'Voting' | 'Committed';
|
|
36
|
-
|
|
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
|
-
*/
|
|
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;
|
|
66
|
-
get isCommitted(): boolean;
|
|
67
|
-
get isPositiveOutcome(): boolean | undefined;
|
|
22
|
+
/** Handle a Quorum-mode envelope. See `BaseProjection.applyEnvelope` for the accepted-only input contract and redelivery/rollback semantics shared by all built-in modes. */
|
|
23
|
+
protected applyMode(envelope: Envelope, protoRegistry: ProtoRegistry): void;
|
|
68
24
|
private setBallot;
|
|
69
25
|
approvalCount(requestId: string): number;
|
|
70
26
|
rejectionCount(requestId: string): number;
|
|
@@ -2,64 +2,14 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.QuorumProjection = void 0;
|
|
4
4
|
const constants_1 = require("../constants");
|
|
5
|
-
const
|
|
6
|
-
class QuorumProjection {
|
|
5
|
+
const base_1 = require("./base");
|
|
6
|
+
class QuorumProjection extends base_1.BaseProjection {
|
|
7
|
+
mode = constants_1.MODE_QUORUM;
|
|
7
8
|
requests = new Map();
|
|
8
9
|
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
|
-
*/
|
|
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 = [];
|
|
24
10
|
phase = 'Pending';
|
|
25
|
-
|
|
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
|
-
*/
|
|
48
|
-
applyEnvelope(envelope, protoRegistry) {
|
|
49
|
-
if (envelope.mode !== constants_1.MODE_QUORUM)
|
|
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
|
-
}
|
|
62
|
-
this.transcript.push(envelope);
|
|
11
|
+
/** Handle a Quorum-mode envelope. See `BaseProjection.applyEnvelope` for the accepted-only input contract and redelivery/rollback semantics shared by all built-in modes. */
|
|
12
|
+
applyMode(envelope, protoRegistry) {
|
|
63
13
|
const payload = protoRegistry.decodeKnownPayload(envelope.mode, envelope.messageType, envelope.payload);
|
|
64
14
|
switch (envelope.messageType) {
|
|
65
15
|
case 'ApprovalRequest': {
|
|
@@ -83,33 +33,10 @@ class QuorumProjection {
|
|
|
83
33
|
this.setBallot(envelope, record.requestId, 'abstain', record.reason);
|
|
84
34
|
break;
|
|
85
35
|
}
|
|
86
|
-
case 'Commitment': {
|
|
87
|
-
this.commitment = payload;
|
|
88
|
-
this.phase = 'Committed';
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
91
36
|
default:
|
|
92
37
|
break;
|
|
93
38
|
}
|
|
94
39
|
}
|
|
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
|
-
}
|
|
103
|
-
get isCommitted() {
|
|
104
|
-
return this.commitment !== undefined;
|
|
105
|
-
}
|
|
106
|
-
get isPositiveOutcome() {
|
|
107
|
-
if (!this.commitment)
|
|
108
|
-
return undefined;
|
|
109
|
-
const val = this.commitment.outcomePositive ??
|
|
110
|
-
this.commitment.outcome_positive;
|
|
111
|
-
return val !== undefined ? Boolean(val) : true;
|
|
112
|
-
}
|
|
113
40
|
setBallot(envelope, requestId, vote, reason) {
|
|
114
41
|
const senderMap = this.ballots.get(requestId) ?? new Map();
|
|
115
42
|
const kept = senderMap.get(envelope.sender);
|
|
@@ -129,7 +56,7 @@ class QuorumProjection {
|
|
|
129
56
|
// by RFC-0011 either; it is parity with RFC-MACP-0007 §5 item 3's
|
|
130
57
|
// explicit first-stands rule for `Vote`, plus macp-runtime's enforced
|
|
131
58
|
// first-wins behaviour.
|
|
132
|
-
|
|
59
|
+
this.recordAnomaly({
|
|
133
60
|
kind: 'duplicate_ballot',
|
|
134
61
|
mode: envelope.mode,
|
|
135
62
|
messageType: envelope.messageType,
|
|
@@ -137,9 +64,7 @@ class QuorumProjection {
|
|
|
137
64
|
sender: envelope.sender,
|
|
138
65
|
subjectId: requestId,
|
|
139
66
|
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);
|
|
67
|
+
});
|
|
143
68
|
return;
|
|
144
69
|
}
|
|
145
70
|
senderMap.set(envelope.sender, { requestId, vote, reason, sender: envelope.sender });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { BaseProjection } from './base';
|
|
2
2
|
import type { Envelope } from '../types';
|
|
3
3
|
import type { ProtoRegistry } from '../proto-registry';
|
|
4
4
|
export interface TaskRecord {
|
|
@@ -33,34 +33,13 @@ export interface TaskFailureRecord {
|
|
|
33
33
|
retryable: boolean;
|
|
34
34
|
sender: string;
|
|
35
35
|
}
|
|
36
|
-
export declare class TaskProjection {
|
|
36
|
+
export declare class TaskProjection extends BaseProjection {
|
|
37
|
+
protected readonly mode = "macp.mode.task.v1";
|
|
37
38
|
readonly tasks: Map<string, TaskRecord>;
|
|
38
39
|
readonly updates: TaskUpdateRecord[];
|
|
39
40
|
readonly completions: TaskCompletionRecord[];
|
|
40
41
|
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
|
-
*/
|
|
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[];
|
|
55
42
|
phase: 'Pending' | 'Requested' | 'InProgress' | 'Completed' | 'Failed' | 'Committed';
|
|
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
43
|
/**
|
|
65
44
|
* The **session-level** active assignment: who currently holds the session's
|
|
66
45
|
* single assignee slot, and which `task_id` they took it on.
|
|
@@ -81,30 +60,8 @@ export declare class TaskProjection {
|
|
|
81
60
|
* the right `TaskRecord.assignee` even on a multi-task transcript.
|
|
82
61
|
*/
|
|
83
62
|
private activeAssignment?;
|
|
84
|
-
/**
|
|
85
|
-
|
|
86
|
-
*
|
|
87
|
-
* Input contract: **accepted-only**, caller-maintained (`Envelope` carries
|
|
88
|
-
* no acceptance marker). Canonical source: `schemas/conformance/README.md`
|
|
89
|
-
* "Notes:", RFC-MACP-0007 §5.3, RFC-MACP-0011 §5. Full rationale and failure
|
|
90
|
-
* mode are documented once, on `BaseProjection.applyEnvelope`
|
|
91
|
-
* (`src/projections/base.ts`), and duplicated here only as a one-line
|
|
92
|
-
* pointer so six independent copies of the same prose cannot drift.
|
|
93
|
-
*
|
|
94
|
-
* Redelivery idempotence: a redelivered envelope (same `message_id`) is a
|
|
95
|
-
* non-event — not appended to `transcript`, not passed to the `switch`.
|
|
96
|
-
* See `BaseProjection.applyEnvelope` for the full RFC-MACP-0006 §3.2
|
|
97
|
-
* citation; duplicated here only as a one-line pointer.
|
|
98
|
-
*/
|
|
99
|
-
applyEnvelope(envelope: Envelope, protoRegistry: ProtoRegistry): void;
|
|
100
|
-
/**
|
|
101
|
-
* True once at least one `ProjectionAnomaly` has been recorded. See
|
|
102
|
-
* `BaseProjection.hasAnomalies` (`src/projections/base.ts`) for the
|
|
103
|
-
* canonical description; duplicated here only as a one-line pointer.
|
|
104
|
-
*/
|
|
105
|
-
get hasAnomalies(): boolean;
|
|
106
|
-
get isCommitted(): boolean;
|
|
107
|
-
get isPositiveOutcome(): boolean | undefined;
|
|
63
|
+
/** Handle a Task-mode envelope. See `BaseProjection.applyEnvelope` for the accepted-only input contract and redelivery/rollback semantics shared by all built-in modes. */
|
|
64
|
+
protected applyMode(envelope: Envelope, protoRegistry: ProtoRegistry): void;
|
|
108
65
|
getTask(taskId: string): TaskRecord | undefined;
|
|
109
66
|
isComplete(taskId: string): boolean;
|
|
110
67
|
isFailed(taskId: string): boolean;
|