@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.
- package/dist/agent-tool-names.d.ts +2 -2
- package/dist/agent-tool-names.js +14 -0
- package/dist/scheduler-envelope.d.ts +190 -0
- package/dist/scheduler-envelope.js +412 -0
- package/dist/server.js +28 -2
- package/dist/tools/campaign-message-preparation.d.ts +25 -0
- package/dist/tools/campaign-message-preparation.js +37 -0
- package/dist/tools/campaign-processing.d.ts +74 -1
- package/dist/tools/campaign-processing.js +57 -1
- package/dist/tools/prompts.d.ts +15 -3
- package/dist/tools/prompts.js +25 -0
- package/dist/tools/refill-executors.d.ts +43 -70
- package/dist/tools/refill-executors.js +201 -273
- package/dist/tools/refill-sends.d.ts +3 -3
- package/dist/tools/refill-sends.js +1 -1
- package/dist/tools/refill-target-plan.js +1 -1
- package/dist/tools/refill-v3-advance-contract.d.ts +618 -0
- package/dist/tools/refill-v3-advance-contract.js +920 -0
- package/dist/tools/refill-v3-advance.d.ts +397 -0
- package/dist/tools/refill-v3-advance.js +75 -0
- package/dist/tools/refill-v3-attention-packet-contract.d.ts +608 -0
- package/dist/tools/refill-v3-attention-packet-contract.js +911 -0
- package/dist/tools/refill-v3-campaign-work.d.ts +324 -0
- package/dist/tools/refill-v3-campaign-work.js +1015 -0
- package/dist/tools/refill-v3-continue-contract.d.ts +433 -0
- package/dist/tools/refill-v3-continue-contract.js +112 -0
- package/dist/tools/refill-v3-continue.d.ts +501 -0
- package/dist/tools/refill-v3-continue.js +654 -0
- package/dist/tools/refill-v3-edit-selected-campaign-contract.d.ts +208 -0
- package/dist/tools/refill-v3-edit-selected-campaign-contract.js +351 -0
- package/dist/tools/refill-v3-edit-selected-campaign.d.ts +232 -0
- package/dist/tools/refill-v3-edit-selected-campaign.js +147 -0
- package/dist/tools/refill-v3-fill-ready-contract.d.ts +198 -0
- package/dist/tools/refill-v3-fill-ready-contract.js +324 -0
- package/dist/tools/refill-v3-fill-ready.d.ts +199 -0
- package/dist/tools/refill-v3-fill-ready.js +136 -0
- package/dist/tools/refill-v3-source-family-dispatch.d.ts +249 -0
- package/dist/tools/refill-v3-source-family-dispatch.js +514 -0
- package/dist/tools/refill-v3-waterfall-contract.d.ts +156 -0
- package/dist/tools/refill-v3-waterfall-contract.js +181 -0
- package/dist/tools/refill-v3-waterfall.d.ts +136 -0
- package/dist/tools/refill-v3-waterfall.js +161 -0
- package/dist/tools/refill-v3-world-state-contract.d.ts +666 -0
- package/dist/tools/refill-v3-world-state-contract.js +790 -0
- package/dist/tools/refill-v3-world-state.d.ts +592 -0
- package/dist/tools/refill-v3-world-state.js +137 -0
- package/dist/tools/registry.d.ts +1710 -3
- package/dist/tools/registry.js +19 -0
- package/package.json +1 -1
- package/skills/refill-sends/SKILL.md +89 -4
- package/skills/refill-sends-waterfall-order/SKILL.md +174 -0
- package/skills/refill-sends-work-campaign/SKILL.md +253 -0
- package/skills/refill-sends-workflow/SKILL.md +112 -5
- 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
|
+
}
|