@sellable/mcp 0.1.793 → 0.1.794

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.
Files changed (54) hide show
  1. package/dist/agent-tool-names.d.ts +2 -2
  2. package/dist/agent-tool-names.js +14 -0
  3. package/dist/scheduler-envelope.d.ts +190 -0
  4. package/dist/scheduler-envelope.js +412 -0
  5. package/dist/server.js +28 -2
  6. package/dist/tools/campaign-message-preparation.d.ts +25 -0
  7. package/dist/tools/campaign-message-preparation.js +37 -0
  8. package/dist/tools/campaign-processing.d.ts +74 -1
  9. package/dist/tools/campaign-processing.js +57 -1
  10. package/dist/tools/prompts.d.ts +15 -3
  11. package/dist/tools/prompts.js +25 -0
  12. package/dist/tools/refill-executors.d.ts +43 -70
  13. package/dist/tools/refill-executors.js +201 -273
  14. package/dist/tools/refill-sends.d.ts +3 -3
  15. package/dist/tools/refill-sends.js +1 -1
  16. package/dist/tools/refill-target-plan.js +1 -1
  17. package/dist/tools/refill-v3-advance-contract.d.ts +618 -0
  18. package/dist/tools/refill-v3-advance-contract.js +920 -0
  19. package/dist/tools/refill-v3-advance.d.ts +397 -0
  20. package/dist/tools/refill-v3-advance.js +75 -0
  21. package/dist/tools/refill-v3-attention-packet-contract.d.ts +608 -0
  22. package/dist/tools/refill-v3-attention-packet-contract.js +911 -0
  23. package/dist/tools/refill-v3-campaign-work.d.ts +324 -0
  24. package/dist/tools/refill-v3-campaign-work.js +1015 -0
  25. package/dist/tools/refill-v3-continue-contract.d.ts +433 -0
  26. package/dist/tools/refill-v3-continue-contract.js +112 -0
  27. package/dist/tools/refill-v3-continue.d.ts +501 -0
  28. package/dist/tools/refill-v3-continue.js +654 -0
  29. package/dist/tools/refill-v3-edit-selected-campaign-contract.d.ts +208 -0
  30. package/dist/tools/refill-v3-edit-selected-campaign-contract.js +351 -0
  31. package/dist/tools/refill-v3-edit-selected-campaign.d.ts +232 -0
  32. package/dist/tools/refill-v3-edit-selected-campaign.js +147 -0
  33. package/dist/tools/refill-v3-fill-ready-contract.d.ts +198 -0
  34. package/dist/tools/refill-v3-fill-ready-contract.js +324 -0
  35. package/dist/tools/refill-v3-fill-ready.d.ts +199 -0
  36. package/dist/tools/refill-v3-fill-ready.js +136 -0
  37. package/dist/tools/refill-v3-source-family-dispatch.d.ts +249 -0
  38. package/dist/tools/refill-v3-source-family-dispatch.js +514 -0
  39. package/dist/tools/refill-v3-waterfall-contract.d.ts +156 -0
  40. package/dist/tools/refill-v3-waterfall-contract.js +181 -0
  41. package/dist/tools/refill-v3-waterfall.d.ts +136 -0
  42. package/dist/tools/refill-v3-waterfall.js +161 -0
  43. package/dist/tools/refill-v3-world-state-contract.d.ts +666 -0
  44. package/dist/tools/refill-v3-world-state-contract.js +790 -0
  45. package/dist/tools/refill-v3-world-state.d.ts +592 -0
  46. package/dist/tools/refill-v3-world-state.js +137 -0
  47. package/dist/tools/registry.d.ts +1710 -3
  48. package/dist/tools/registry.js +19 -0
  49. package/package.json +1 -1
  50. package/skills/refill-sends/SKILL.md +89 -4
  51. package/skills/refill-sends-waterfall-order/SKILL.md +174 -0
  52. package/skills/refill-sends-work-campaign/SKILL.md +253 -0
  53. package/skills/refill-sends-workflow/SKILL.md +112 -5
  54. package/skills/refill-sends-workflow/core/flow.v3.json +209 -0
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Refill V3 selected-campaign EDIT result PACKAGE seam (Phase 146.5 Plan 09 T2,
3
+ * RV3EDIT-001).
4
+ *
5
+ * `refill_v3_edit_selected_campaign` is the fourth public V3 tool, so it needs a
6
+ * package-reachable result contract for the same reason the other three do: the
7
+ * INSTALLED client decodes the wire body, and a wrapper that declares its own
8
+ * shape drifts from the backend the moment either side changes.
9
+ *
10
+ * It is dependency-free apart from the sibling packet seam, which owns every
11
+ * shared vocabulary this module consumes. In particular the terminality question
12
+ * ("is there simply nothing left to edit?") is answered by the SHARED
13
+ * `REFILL_V3_EXHAUSTION_PREDICATE` rather than by a second truth table here.
14
+ *
15
+ * Constraints (`A02-NO-ZOD`): no app `src/**` import, no validation library,
16
+ * hand-rolled guards plus as-const JSON-Schema literals.
17
+ */
18
+ import { type RefillV3ExhaustionInput, type RefillV3ExhaustionVerdict } from "./refill-v3-attention-packet-contract.js";
19
+ /**
20
+ * The THREE closed edit actions, DERIVED from the shared attention-action
21
+ * vocabulary by suffix selection so this module can never grow a fourth action
22
+ * the AI seam does not know about.
23
+ *
24
+ * Rerun, same-meaning regeneration, and the guarded rubric trial are
25
+ * deliberately ABSENT: they are not edit-tool variants and keep their existing
26
+ * deterministic owners.
27
+ */
28
+ export declare const REFILL_V3_EDIT_ACTIONS: RefillV3CampaignEditAction[];
29
+ export type RefillV3CampaignEditAction = "repair_campaign_config" | "patch_filter_criteria" | "patch_source_criteria";
30
+ export declare const REFILL_V3_EDIT_OUTCOMES: readonly ["applied", "replayed", "rolled_back", "blocked"];
31
+ export type RefillV3CampaignEditOutcome = (typeof REFILL_V3_EDIT_OUTCOMES)[number];
32
+ /**
33
+ * Every closed BUSINESS refusal. A member of this list is an HTTP 200 result
34
+ * with its typed reason; an infrastructure failure is non-2xx and never appears
35
+ * here, because a caller that reads `blocked` from a database outage will edit
36
+ * the campaign to work around a database problem.
37
+ */
38
+ export declare const REFILL_V3_EDIT_REASONS: readonly ["cross_workspace_scope", "foreign_campaign_scope", "foreign_table_scope", "stale_campaign_version", "stale_evidence_fingerprint", "stale_prior_fingerprint", "disallowed_action", "protected_field", "value_shape_mismatch", "protected_invariant_weakened", "health_evidence_not_editable", "reobservation_scope_missing", "idempotency_input_changed", "no_edit_available", "uncertain_delegated_effect"];
39
+ export type RefillV3CampaignEditReason = (typeof REFILL_V3_EDIT_REASONS)[number];
40
+ export declare const REFILL_V3_EDIT_REPLAY_STATES: readonly ["first", "exact_replay", "not_applicable"];
41
+ export type RefillV3CampaignEditReplayState = (typeof REFILL_V3_EDIT_REPLAY_STATES)[number];
42
+ /**
43
+ * Fields this authority may never touch, whatever the action. `update_campaign`
44
+ * accepts every one of them; the point of this wrapper is that it does not.
45
+ */
46
+ export declare const REFILL_V3_EDIT_PROTECTED_FIELDS: readonly ["name", "campaignBrief", "offerPositioning", "senderIds", "currentStep", "currentStepTransition", "clearCurrentStepIfMatches", "interactionMode", "flowVersion", "leadSourceProvider", "selectedLeadListId", "rubric", "watchNarration"];
47
+ export type RefillV3EditProtectedField = (typeof REFILL_V3_EDIT_PROTECTED_FIELDS)[number];
48
+ /**
49
+ * The MINIMAL per-action field allowlist. It is deliberately small: a field
50
+ * enters it only with evidence that a selected packet can prove the delta, and
51
+ * an action/field pair outside this table is `value_shape_mismatch`.
52
+ */
53
+ export declare const REFILL_V3_EDIT_FIELD_ALLOWLIST: Readonly<Record<RefillV3CampaignEditAction, readonly string[]>>;
54
+ export declare const REFILL_V3_EDIT_SCHEMA_NAMESPACE: "refill_v3_campaign_edit";
55
+ export declare const REFILL_V3_EDIT_SCHEMA_MAJOR = 1;
56
+ export declare const REFILL_V3_EDIT_SCHEMA_VERSION: "refill_v3_campaign_edit.v1.0";
57
+ export type RefillV3CampaignEditRollback = {
58
+ available: boolean;
59
+ input: Record<string, unknown> | null;
60
+ result: string | null;
61
+ };
62
+ export type RefillV3CampaignEditReceipt = {
63
+ scopeEcho: string;
64
+ idempotencyKey: string;
65
+ effectKey: string;
66
+ beforeFingerprint: string;
67
+ afterFingerprint: string;
68
+ changedFields: string[];
69
+ replayState: RefillV3CampaignEditReplayState;
70
+ rollback: RefillV3CampaignEditRollback;
71
+ };
72
+ export type RefillV3CampaignEditReobservation = {
73
+ senderId: string;
74
+ date: string;
75
+ required: true;
76
+ lanes: string[];
77
+ };
78
+ export type RefillV3CampaignEditResult = {
79
+ outcome: RefillV3CampaignEditOutcome;
80
+ action: RefillV3CampaignEditAction;
81
+ reason: RefillV3CampaignEditReason | null;
82
+ receipt: RefillV3CampaignEditReceipt | null;
83
+ reobservation: RefillV3CampaignEditReobservation | null;
84
+ detail: string;
85
+ };
86
+ export declare const REFILL_V3_CAMPAIGN_EDIT_OUTPUT_SCHEMA: Readonly<{
87
+ readonly type: "object";
88
+ readonly additionalProperties: false;
89
+ readonly required: readonly ["schemaVersion", "outcome", "action", "reason", "receipt", "reobservation", "detail"];
90
+ readonly properties: {
91
+ readonly schemaVersion: {
92
+ readonly type: "string";
93
+ readonly const: "refill_v3_campaign_edit.v1.0";
94
+ };
95
+ readonly outcome: {
96
+ readonly type: "string";
97
+ readonly enum: readonly ["applied", "replayed", "rolled_back", "blocked"];
98
+ };
99
+ readonly action: {
100
+ readonly type: "string";
101
+ readonly enum: readonly RefillV3CampaignEditAction[];
102
+ };
103
+ readonly reason: {
104
+ readonly type: readonly ["string", "null"];
105
+ readonly enum: readonly ["cross_workspace_scope", "foreign_campaign_scope", "foreign_table_scope", "stale_campaign_version", "stale_evidence_fingerprint", "stale_prior_fingerprint", "disallowed_action", "protected_field", "value_shape_mismatch", "protected_invariant_weakened", "health_evidence_not_editable", "reobservation_scope_missing", "idempotency_input_changed", "no_edit_available", "uncertain_delegated_effect", null];
106
+ };
107
+ readonly receipt: {
108
+ readonly type: readonly ["object", "null"];
109
+ readonly additionalProperties: false;
110
+ readonly required: readonly ["scopeEcho", "idempotencyKey", "effectKey", "beforeFingerprint", "afterFingerprint", "changedFields", "replayState", "rollback"];
111
+ readonly properties: {
112
+ readonly scopeEcho: {
113
+ readonly type: "string";
114
+ readonly minLength: 1;
115
+ };
116
+ readonly idempotencyKey: {
117
+ readonly type: "string";
118
+ readonly minLength: 1;
119
+ };
120
+ readonly effectKey: {
121
+ readonly type: "string";
122
+ readonly minLength: 1;
123
+ };
124
+ readonly beforeFingerprint: {
125
+ readonly type: "string";
126
+ readonly minLength: 1;
127
+ };
128
+ readonly afterFingerprint: {
129
+ readonly type: "string";
130
+ readonly minLength: 1;
131
+ };
132
+ readonly changedFields: {
133
+ readonly type: "array";
134
+ readonly items: {
135
+ readonly type: "string";
136
+ readonly minLength: 1;
137
+ };
138
+ };
139
+ readonly replayState: {
140
+ readonly type: "string";
141
+ readonly enum: readonly ["first", "exact_replay", "not_applicable"];
142
+ };
143
+ readonly rollback: {
144
+ readonly type: "object";
145
+ readonly additionalProperties: false;
146
+ readonly required: readonly ["available", "input", "result"];
147
+ readonly properties: {
148
+ readonly available: {
149
+ readonly type: "boolean";
150
+ };
151
+ readonly input: {
152
+ readonly type: readonly ["object", "null"];
153
+ };
154
+ readonly result: {
155
+ readonly type: readonly ["string", "null"];
156
+ };
157
+ };
158
+ };
159
+ };
160
+ };
161
+ readonly reobservation: {
162
+ readonly type: readonly ["object", "null"];
163
+ readonly additionalProperties: false;
164
+ readonly required: readonly ["senderId", "date", "required", "lanes"];
165
+ readonly properties: {
166
+ readonly senderId: {
167
+ readonly type: "string";
168
+ readonly minLength: 1;
169
+ };
170
+ readonly date: {
171
+ readonly type: "string";
172
+ readonly pattern: "^\\d{4}-\\d{2}-\\d{2}$";
173
+ };
174
+ readonly required: {
175
+ readonly type: "boolean";
176
+ readonly const: true;
177
+ };
178
+ readonly lanes: {
179
+ readonly type: "array";
180
+ readonly items: {
181
+ readonly type: "string";
182
+ readonly minLength: 1;
183
+ };
184
+ };
185
+ };
186
+ };
187
+ readonly detail: {
188
+ readonly type: "string";
189
+ };
190
+ };
191
+ }>;
192
+ /**
193
+ * Sole unknown-value decoder for the edit result.
194
+ *
195
+ * Strict-in, lossless. `null` means "not this contract" — including an
196
+ * infrastructure error envelope, a raw campaign row leak, and an incompatible
197
+ * schema major — so a caller can never mistake an outage for a business answer.
198
+ */
199
+ export declare function decodeRefillV3CampaignEditResult(value: unknown): RefillV3CampaignEditResult | null;
200
+ export declare function encodeRefillV3CampaignEditResult(result: RefillV3CampaignEditResult): Record<string, unknown>;
201
+ /**
202
+ * "Nothing left to edit" is a TERMINALITY claim, and terminality has exactly one
203
+ * owner in this phase. Routing it through the shared predicate is what stops the
204
+ * edit surface from growing a second, quietly divergent exhaustion rule.
205
+ */
206
+ export declare function refillV3EditTerminalityVerdict(evidence: RefillV3ExhaustionInput): RefillV3ExhaustionVerdict;
207
+ /** Transport gate, shared with every sibling contract. */
208
+ export declare function resolveRefillV3EditSchemaVersion(value: unknown): import("./refill-v3-attention-packet-contract.js").RefillV3SchemaVersionSupport;
@@ -0,0 +1,351 @@
1
+ /**
2
+ * Refill V3 selected-campaign EDIT result PACKAGE seam (Phase 146.5 Plan 09 T2,
3
+ * RV3EDIT-001).
4
+ *
5
+ * `refill_v3_edit_selected_campaign` is the fourth public V3 tool, so it needs a
6
+ * package-reachable result contract for the same reason the other three do: the
7
+ * INSTALLED client decodes the wire body, and a wrapper that declares its own
8
+ * shape drifts from the backend the moment either side changes.
9
+ *
10
+ * It is dependency-free apart from the sibling packet seam, which owns every
11
+ * shared vocabulary this module consumes. In particular the terminality question
12
+ * ("is there simply nothing left to edit?") is answered by the SHARED
13
+ * `REFILL_V3_EXHAUSTION_PREDICATE` rather than by a second truth table here.
14
+ *
15
+ * Constraints (`A02-NO-ZOD`): no app `src/**` import, no validation library,
16
+ * hand-rolled guards plus as-const JSON-Schema literals.
17
+ */
18
+ import { REFILL_V3_CAMPAIGN_ATTENTION_ACTIONS, REFILL_V3_EXHAUSTION_PREDICATE, resolveRefillV3SchemaVersion, } from "./refill-v3-attention-packet-contract.js";
19
+ // ───────────────────────────────────────────────────────────────────────────
20
+ // Closed vocabularies
21
+ // ───────────────────────────────────────────────────────────────────────────
22
+ /**
23
+ * The THREE closed edit actions, DERIVED from the shared attention-action
24
+ * vocabulary by suffix selection so this module can never grow a fourth action
25
+ * the AI seam does not know about.
26
+ *
27
+ * Rerun, same-meaning regeneration, and the guarded rubric trial are
28
+ * deliberately ABSENT: they are not edit-tool variants and keep their existing
29
+ * deterministic owners.
30
+ */
31
+ export const REFILL_V3_EDIT_ACTIONS = REFILL_V3_CAMPAIGN_ATTENTION_ACTIONS.filter((action) => action === "repair_campaign_config" ||
32
+ action === "patch_filter_criteria" ||
33
+ action === "patch_source_criteria");
34
+ export const REFILL_V3_EDIT_OUTCOMES = [
35
+ "applied",
36
+ "replayed",
37
+ "rolled_back",
38
+ "blocked",
39
+ ];
40
+ /**
41
+ * Every closed BUSINESS refusal. A member of this list is an HTTP 200 result
42
+ * with its typed reason; an infrastructure failure is non-2xx and never appears
43
+ * here, because a caller that reads `blocked` from a database outage will edit
44
+ * the campaign to work around a database problem.
45
+ */
46
+ export const REFILL_V3_EDIT_REASONS = [
47
+ "cross_workspace_scope",
48
+ "foreign_campaign_scope",
49
+ "foreign_table_scope",
50
+ "stale_campaign_version",
51
+ "stale_evidence_fingerprint",
52
+ "stale_prior_fingerprint",
53
+ "disallowed_action",
54
+ "protected_field",
55
+ "value_shape_mismatch",
56
+ "protected_invariant_weakened",
57
+ "health_evidence_not_editable",
58
+ "reobservation_scope_missing",
59
+ "idempotency_input_changed",
60
+ "no_edit_available",
61
+ "uncertain_delegated_effect",
62
+ ];
63
+ export const REFILL_V3_EDIT_REPLAY_STATES = [
64
+ "first",
65
+ "exact_replay",
66
+ "not_applicable",
67
+ ];
68
+ /**
69
+ * Fields this authority may never touch, whatever the action. `update_campaign`
70
+ * accepts every one of them; the point of this wrapper is that it does not.
71
+ */
72
+ export const REFILL_V3_EDIT_PROTECTED_FIELDS = [
73
+ "name",
74
+ "campaignBrief",
75
+ "offerPositioning",
76
+ "senderIds",
77
+ "currentStep",
78
+ "currentStepTransition",
79
+ "clearCurrentStepIfMatches",
80
+ "interactionMode",
81
+ "flowVersion",
82
+ "leadSourceProvider",
83
+ "selectedLeadListId",
84
+ "rubric",
85
+ "watchNarration",
86
+ ];
87
+ /**
88
+ * The MINIMAL per-action field allowlist. It is deliberately small: a field
89
+ * enters it only with evidence that a selected packet can prove the delta, and
90
+ * an action/field pair outside this table is `value_shape_mismatch`.
91
+ */
92
+ export const REFILL_V3_EDIT_FIELD_ALLOWLIST = Object.freeze({
93
+ repair_campaign_config: ["enableICPFilters", "useMessagingTemplate"],
94
+ patch_filter_criteria: ["enableICPFilters"],
95
+ patch_source_criteria: ["leadSourceType"],
96
+ });
97
+ export const REFILL_V3_EDIT_SCHEMA_NAMESPACE = "refill_v3_campaign_edit";
98
+ export const REFILL_V3_EDIT_SCHEMA_MAJOR = 1;
99
+ export const REFILL_V3_EDIT_SCHEMA_VERSION = "refill_v3_campaign_edit.v1.0";
100
+ // ───────────────────────────────────────────────────────────────────────────
101
+ // Output schema
102
+ // ───────────────────────────────────────────────────────────────────────────
103
+ export const REFILL_V3_CAMPAIGN_EDIT_OUTPUT_SCHEMA = Object.freeze({
104
+ type: "object",
105
+ additionalProperties: false,
106
+ required: [
107
+ "schemaVersion",
108
+ "outcome",
109
+ "action",
110
+ "reason",
111
+ "receipt",
112
+ "reobservation",
113
+ "detail",
114
+ ],
115
+ properties: {
116
+ schemaVersion: { type: "string", const: REFILL_V3_EDIT_SCHEMA_VERSION },
117
+ outcome: { type: "string", enum: [...REFILL_V3_EDIT_OUTCOMES] },
118
+ action: { type: "string", enum: [...REFILL_V3_EDIT_ACTIONS] },
119
+ reason: {
120
+ type: ["string", "null"],
121
+ enum: [...REFILL_V3_EDIT_REASONS, null],
122
+ },
123
+ receipt: {
124
+ type: ["object", "null"],
125
+ additionalProperties: false,
126
+ required: [
127
+ "scopeEcho",
128
+ "idempotencyKey",
129
+ "effectKey",
130
+ "beforeFingerprint",
131
+ "afterFingerprint",
132
+ "changedFields",
133
+ "replayState",
134
+ "rollback",
135
+ ],
136
+ properties: {
137
+ scopeEcho: { type: "string", minLength: 1 },
138
+ idempotencyKey: { type: "string", minLength: 1 },
139
+ effectKey: { type: "string", minLength: 1 },
140
+ beforeFingerprint: { type: "string", minLength: 1 },
141
+ afterFingerprint: { type: "string", minLength: 1 },
142
+ changedFields: {
143
+ type: "array",
144
+ items: { type: "string", minLength: 1 },
145
+ },
146
+ replayState: {
147
+ type: "string",
148
+ enum: [...REFILL_V3_EDIT_REPLAY_STATES],
149
+ },
150
+ rollback: {
151
+ type: "object",
152
+ additionalProperties: false,
153
+ required: ["available", "input", "result"],
154
+ properties: {
155
+ available: { type: "boolean" },
156
+ input: { type: ["object", "null"] },
157
+ result: { type: ["string", "null"] },
158
+ },
159
+ },
160
+ },
161
+ },
162
+ reobservation: {
163
+ type: ["object", "null"],
164
+ additionalProperties: false,
165
+ required: ["senderId", "date", "required", "lanes"],
166
+ properties: {
167
+ senderId: { type: "string", minLength: 1 },
168
+ date: { type: "string", pattern: "^\\d{4}-\\d{2}-\\d{2}$" },
169
+ required: { type: "boolean", const: true },
170
+ lanes: { type: "array", items: { type: "string", minLength: 1 } },
171
+ },
172
+ },
173
+ detail: { type: "string" },
174
+ },
175
+ });
176
+ // ───────────────────────────────────────────────────────────────────────────
177
+ // Decoding
178
+ // ───────────────────────────────────────────────────────────────────────────
179
+ const RESULT_KEYS = new Set([
180
+ "schemaVersion",
181
+ "outcome",
182
+ "action",
183
+ "reason",
184
+ "receipt",
185
+ "reobservation",
186
+ "detail",
187
+ ]);
188
+ const RECEIPT_KEYS = new Set([
189
+ "scopeEcho",
190
+ "idempotencyKey",
191
+ "effectKey",
192
+ "beforeFingerprint",
193
+ "afterFingerprint",
194
+ "changedFields",
195
+ "replayState",
196
+ "rollback",
197
+ ]);
198
+ const ROLLBACK_KEYS = new Set(["available", "input", "result"]);
199
+ const REOBSERVATION_KEYS = new Set(["senderId", "date", "required", "lanes"]);
200
+ function isRecord(value) {
201
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
202
+ }
203
+ function isNonEmptyString(value) {
204
+ return typeof value === "string" && value.length > 0;
205
+ }
206
+ function isOneOf(value, options) {
207
+ return (typeof value === "string" && options.includes(value));
208
+ }
209
+ function decodeRollback(value) {
210
+ if (!isRecord(value))
211
+ return null;
212
+ if (Object.keys(value).some((key) => !ROLLBACK_KEYS.has(key)))
213
+ return null;
214
+ if (typeof value.available !== "boolean")
215
+ return null;
216
+ if (!(value.input === null || isRecord(value.input)))
217
+ return null;
218
+ if (!(value.result === null || isNonEmptyString(value.result)))
219
+ return null;
220
+ return {
221
+ available: value.available,
222
+ input: value.input,
223
+ result: value.result,
224
+ };
225
+ }
226
+ function decodeReceipt(value) {
227
+ if (!isRecord(value))
228
+ return null;
229
+ if (Object.keys(value).some((key) => !RECEIPT_KEYS.has(key)))
230
+ return null;
231
+ for (const key of RECEIPT_KEYS) {
232
+ if (!(key in value))
233
+ return null;
234
+ }
235
+ if (!isNonEmptyString(value.scopeEcho) ||
236
+ !isNonEmptyString(value.idempotencyKey) ||
237
+ !isNonEmptyString(value.effectKey) ||
238
+ !isNonEmptyString(value.beforeFingerprint) ||
239
+ !isNonEmptyString(value.afterFingerprint) ||
240
+ !isOneOf(value.replayState, REFILL_V3_EDIT_REPLAY_STATES) ||
241
+ !Array.isArray(value.changedFields) ||
242
+ !value.changedFields.every(isNonEmptyString)) {
243
+ return null;
244
+ }
245
+ const rollback = decodeRollback(value.rollback);
246
+ if (rollback === null)
247
+ return null;
248
+ return {
249
+ scopeEcho: value.scopeEcho,
250
+ idempotencyKey: value.idempotencyKey,
251
+ effectKey: value.effectKey,
252
+ beforeFingerprint: value.beforeFingerprint,
253
+ afterFingerprint: value.afterFingerprint,
254
+ changedFields: [...value.changedFields],
255
+ replayState: value.replayState,
256
+ rollback,
257
+ };
258
+ }
259
+ function decodeReobservation(value) {
260
+ if (!isRecord(value))
261
+ return null;
262
+ if (Object.keys(value).some((key) => !REOBSERVATION_KEYS.has(key)))
263
+ return null;
264
+ if (!isNonEmptyString(value.senderId) ||
265
+ typeof value.date !== "string" ||
266
+ !/^\d{4}-\d{2}-\d{2}$/.test(value.date) ||
267
+ // `required` is a CONSTANT true: an optional re-observation is not a
268
+ // re-observation obligation at all.
269
+ value.required !== true ||
270
+ !Array.isArray(value.lanes) ||
271
+ !value.lanes.every(isNonEmptyString)) {
272
+ return null;
273
+ }
274
+ return {
275
+ senderId: value.senderId,
276
+ date: value.date,
277
+ required: true,
278
+ lanes: [...value.lanes],
279
+ };
280
+ }
281
+ /**
282
+ * Sole unknown-value decoder for the edit result.
283
+ *
284
+ * Strict-in, lossless. `null` means "not this contract" — including an
285
+ * infrastructure error envelope, a raw campaign row leak, and an incompatible
286
+ * schema major — so a caller can never mistake an outage for a business answer.
287
+ */
288
+ export function decodeRefillV3CampaignEditResult(value) {
289
+ if (!isRecord(value))
290
+ return null;
291
+ if (Object.keys(value).some((key) => !RESULT_KEYS.has(key)))
292
+ return null;
293
+ for (const key of RESULT_KEYS) {
294
+ if (!(key in value))
295
+ return null;
296
+ }
297
+ if (!resolveRefillV3EditSchemaVersion(value.schemaVersion).supported) {
298
+ return null;
299
+ }
300
+ if (!isOneOf(value.outcome, REFILL_V3_EDIT_OUTCOMES) ||
301
+ !isOneOf(value.action, REFILL_V3_EDIT_ACTIONS) ||
302
+ typeof value.detail !== "string") {
303
+ return null;
304
+ }
305
+ if (!(value.reason === null || isOneOf(value.reason, REFILL_V3_EDIT_REASONS))) {
306
+ return null;
307
+ }
308
+ let receipt = null;
309
+ if (value.receipt !== null) {
310
+ receipt = decodeReceipt(value.receipt);
311
+ if (receipt === null)
312
+ return null;
313
+ }
314
+ let reobservation = null;
315
+ if (value.reobservation !== null) {
316
+ reobservation = decodeReobservation(value.reobservation);
317
+ if (reobservation === null)
318
+ return null;
319
+ }
320
+ return {
321
+ outcome: value.outcome,
322
+ action: value.action,
323
+ reason: value.reason,
324
+ receipt,
325
+ reobservation,
326
+ detail: value.detail,
327
+ };
328
+ }
329
+ export function encodeRefillV3CampaignEditResult(result) {
330
+ return {
331
+ schemaVersion: REFILL_V3_EDIT_SCHEMA_VERSION,
332
+ outcome: result.outcome,
333
+ action: result.action,
334
+ reason: result.reason,
335
+ receipt: result.receipt,
336
+ reobservation: result.reobservation,
337
+ detail: result.detail,
338
+ };
339
+ }
340
+ /**
341
+ * "Nothing left to edit" is a TERMINALITY claim, and terminality has exactly one
342
+ * owner in this phase. Routing it through the shared predicate is what stops the
343
+ * edit surface from growing a second, quietly divergent exhaustion rule.
344
+ */
345
+ export function refillV3EditTerminalityVerdict(evidence) {
346
+ return REFILL_V3_EXHAUSTION_PREDICATE.evaluate(evidence);
347
+ }
348
+ /** Transport gate, shared with every sibling contract. */
349
+ export function resolveRefillV3EditSchemaVersion(value) {
350
+ return resolveRefillV3SchemaVersion(value, REFILL_V3_EDIT_SCHEMA_NAMESPACE, REFILL_V3_EDIT_SCHEMA_MAJOR);
351
+ }