macp-sdk-typescript 0.9.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.
@@ -2,66 +2,16 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DecisionProjection = void 0;
4
4
  const constants_1 = require("../constants");
5
- const logging_1 = require("../logging");
6
- class DecisionProjection {
5
+ const base_1 = require("./base");
6
+ class DecisionProjection extends base_1.BaseProjection {
7
+ mode = constants_1.MODE_DECISION;
7
8
  proposals = new Map();
8
9
  evaluations = [];
9
10
  objections = [];
10
11
  votes = new Map();
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
- */
17
- transcript = [];
18
- /**
19
- * Cardinality anomalies recorded while replaying this projection's accepted
20
- * transcript (e.g. a duplicate `Vote` from the same sender for the same
21
- * `proposal_id`). See `BaseProjection.anomalies` (`src/projections/base.ts`)
22
- * for the canonical description; duplicated here only as a one-line
23
- * pointer.
24
- */
25
- anomalies = [];
26
12
  phase = 'Proposal';
27
- commitment;
28
- /**
29
- * `message_id`s already applied to this projection. See
30
- * `BaseProjection.seenMessageIds` (`src/projections/base.ts`) for the full
31
- * redelivery-idempotence rationale (RFC-MACP-0006 §3.2); duplicated here
32
- * only as a one-line pointer.
33
- */
34
- seenMessageIds = new Set();
35
- /**
36
- * Apply one envelope to this projection's in-process state.
37
- *
38
- * Input contract: **accepted-only**, caller-maintained (`Envelope` carries
39
- * no acceptance marker). Canonical source: `schemas/conformance/README.md`
40
- * "Notes:", RFC-MACP-0007 §5.3, RFC-MACP-0011 §5. Full rationale and failure
41
- * mode are documented once, on `BaseProjection.applyEnvelope`
42
- * (`src/projections/base.ts`), and duplicated here only as a one-line
43
- * pointer so six independent copies of the same prose cannot drift.
44
- *
45
- * Redelivery idempotence: a redelivered envelope (same `message_id`) is a
46
- * non-event — not appended to `transcript`, not passed to the `switch`.
47
- * See `BaseProjection.applyEnvelope` for the full RFC-MACP-0006 §3.2
48
- * citation; duplicated here only as a one-line pointer.
49
- */
50
- applyEnvelope(envelope, protoRegistry) {
51
- if (envelope.mode !== constants_1.MODE_DECISION)
52
- return;
53
- if (envelope.messageId) {
54
- if (this.seenMessageIds.has(envelope.messageId)) {
55
- logging_1.logger.debug('projection redelivery ignored', {
56
- messageId: envelope.messageId,
57
- mode: envelope.mode,
58
- messageType: envelope.messageType,
59
- });
60
- return;
61
- }
62
- this.seenMessageIds.add(envelope.messageId);
63
- }
64
- this.transcript.push(envelope);
13
+ /** Handle a Decision-mode envelope. See `BaseProjection.applyEnvelope` for the accepted-only input contract and redelivery/rollback semantics shared by all built-in modes. */
14
+ applyMode(envelope, protoRegistry) {
65
15
  const payload = protoRegistry.decodeKnownPayload(envelope.mode, envelope.messageType, envelope.payload);
66
16
  switch (envelope.messageType) {
67
17
  case 'Proposal': {
@@ -94,7 +44,7 @@ class DecisionProjection {
94
44
  // runtime NACKs the duplicate (macp-runtime decision.rs), so reaching
95
45
  // here means the transcript was not filtered to a conforming
96
46
  // runtime's accepted history.
97
- const anomaly = {
47
+ this.recordAnomaly({
98
48
  kind: 'duplicate_vote',
99
49
  mode: envelope.mode,
100
50
  messageType: envelope.messageType,
@@ -102,9 +52,7 @@ class DecisionProjection {
102
52
  sender: envelope.sender,
103
53
  subjectId: record.proposalId,
104
54
  detail: `sender ${envelope.sender} already voted '${kept.vote}' on proposal ${record.proposalId}; discarded '${record.vote}'`,
105
- };
106
- this.anomalies.push(anomaly);
107
- logging_1.logger.warn('projection anomaly', anomaly);
55
+ });
108
56
  break;
109
57
  }
110
58
  bySender.set(envelope.sender, { ...record, sender: envelope.sender });
@@ -128,33 +76,10 @@ class DecisionProjection {
128
76
  }
129
77
  break;
130
78
  }
131
- case 'Commitment': {
132
- this.commitment = payload;
133
- this.phase = 'Committed';
134
- break;
135
- }
136
79
  default:
137
80
  break;
138
81
  }
139
82
  }
140
- /**
141
- * True once at least one `ProjectionAnomaly` has been recorded. See
142
- * `BaseProjection.hasAnomalies` (`src/projections/base.ts`) for the
143
- * canonical description; duplicated here only as a one-line pointer.
144
- */
145
- get hasAnomalies() {
146
- return this.anomalies.length > 0;
147
- }
148
- get isCommitted() {
149
- return this.commitment !== undefined;
150
- }
151
- get isPositiveOutcome() {
152
- if (!this.commitment)
153
- return undefined;
154
- const val = this.commitment.outcomePositive ??
155
- this.commitment.outcome_positive;
156
- return val !== undefined ? Boolean(val) : true;
157
- }
158
83
  voteTotals() {
159
84
  const totals = {};
160
85
  for (const [proposalId, senderVotes] of this.votes.entries()) {
@@ -1,4 +1,4 @@
1
- import type { ProjectionAnomaly } from './base';
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
- commitment?: Record<string, unknown>;
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 logging_1 = require("../logging");
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
- 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
- */
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 type { ProjectionAnomaly } from './base';
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
- commitment?: Record<string, unknown>;
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 logging_1 = require("../logging");
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
- commitment;
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 type { ProjectionAnomaly } from './base';
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
- 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
- */
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 logging_1 = require("../logging");
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
- 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
- */
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
- const anomaly = {
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 });