@sellable/install 0.1.363 → 0.1.365-phase111.20260720162325

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.
@@ -3712,8 +3712,7 @@ function upsertCodexMcpServerConfig(content, opts) {
3712
3712
  content,
3713
3713
  "mcp_servers.sellable",
3714
3714
  `[mcp_servers.sellable]
3715
- url = ${quoteToml(withHostedWatchModeDriver(opts.hostedUrl, "codex"))}
3716
- default_tools_approval_mode = "approve"`
3715
+ url = ${quoteToml(withHostedWatchModeDriver(opts.hostedUrl, "codex"))}`
3717
3716
  );
3718
3717
  content = removeTomlSection(content, "mcp_servers.sellable.env");
3719
3718
  return content;
@@ -3725,8 +3724,7 @@ default_tools_approval_mode = "approve"`
3725
3724
  "mcp_servers.sellable",
3726
3725
  `[mcp_servers.sellable]
3727
3726
  command = ${quoteToml(command)}
3728
- args = ${tomlArray(args)}
3729
- default_tools_approval_mode = "approve"`
3727
+ args = ${tomlArray(args)}`
3730
3728
  );
3731
3729
  content = upsertTomlTable(
3732
3730
  content,
@@ -3740,9 +3738,6 @@ ${tomlEnvTable("codex", opts)}`
3740
3738
  function codexMcpServerMatches(content, opts) {
3741
3739
  const server = readTomlTable(content, "mcp_servers.sellable");
3742
3740
  if (!server) return false;
3743
- if (!server.includes('default_tools_approval_mode = "approve"')) {
3744
- return false;
3745
- }
3746
3741
  if (opts.server === "hosted") {
3747
3742
  return (
3748
3743
  server.includes(
@@ -3,13 +3,13 @@ FROM ${HERMES_IMAGE}
3
3
 
4
4
  USER root
5
5
 
6
- ARG INSTALLER_PACKAGE=@sellable/install@0.1.363
7
- ARG MCP_PACKAGE=@sellable/mcp@0.1.620
6
+ ARG INSTALLER_PACKAGE=@sellable/install@0.1.365-phase111.20260720162325
7
+ ARG MCP_PACKAGE=@sellable/mcp@0.1.626-phase111.20260720143315
8
8
  ARG INSTALLER_INTEGRITY
9
- ARG MCP_INTEGRITY=sha512-N+2clnLHWAlvoXCjX10HaeEUMPLqZQHIX+x/2e1CSJbqb56CU1SAtVdwmhXyha7+MQT0DZF7pm2+WnoZKFEzOw==
9
+ ARG MCP_INTEGRITY=sha512-pP5LMKh67n9YUXJV8WPSY++130BTXSmnYvHvtXam502ihtWM+ziUGfWqsydFB1nSSTb9GBbFNJQrfEeh+ANyKA==
10
10
 
11
- RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.363" \
12
- && test "${MCP_PACKAGE}" = "@sellable/mcp@0.1.620" \
11
+ RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.365-phase111.20260720162325" \
12
+ && test "${MCP_PACKAGE}" = "@sellable/mcp@0.1.626-phase111.20260720143315" \
13
13
  && test -n "${INSTALLER_INTEGRITY}" \
14
14
  && test "$(npm view "${INSTALLER_PACKAGE}" dist.integrity)" = "${INSTALLER_INTEGRITY}" \
15
15
  && test "$(npm view "${MCP_PACKAGE}" dist.integrity)" = "${MCP_INTEGRITY}" \
@@ -23,17 +23,13 @@ RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.363" \
23
23
  && mkdir -p /opt/sellable-agent/install-root /opt/sellable-agent/mcp-root \
24
24
  && npm install --prefix /opt/sellable-agent/install-root --omit=dev --ignore-scripts --no-audit --no-fund "${INSTALLER_PACKAGE}" \
25
25
  && npm install --prefix /opt/sellable-agent/mcp-root --omit=dev --ignore-scripts --no-audit --no-fund "${MCP_PACKAGE}" \
26
- && node --input-type=module -e "import { readFileSync } from 'node:fs'; const install = JSON.parse(readFileSync('/opt/sellable-agent/install-root/node_modules/@sellable/install/package.json')); const mcp = JSON.parse(readFileSync('/opt/sellable-agent/mcp-root/node_modules/@sellable/mcp/package.json')); if (install.name !== '@sellable/install' || install.version !== '0.1.363' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.620') throw new Error('package identity rejected');" \
26
+ && node --input-type=module -e "import { readFileSync } from 'node:fs'; const install = JSON.parse(readFileSync('/opt/sellable-agent/install-root/node_modules/@sellable/install/package.json')); const mcp = JSON.parse(readFileSync('/opt/sellable-agent/mcp-root/node_modules/@sellable/mcp/package.json')); if (install.name !== '@sellable/install' || install.version !== '0.1.365-phase111.20260720162325' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.626-phase111.20260720143315') throw new Error('package identity rejected');" \
27
27
  && node --input-type=module -e "import { buildExternalRuntimeClosure } from '/opt/sellable-agent/install-root/node_modules/@sellable/install/lib/sellable-agent/external-runtime-builder.mjs'; const built = buildExternalRuntimeClosure({ mcpPackageRoot: '/opt/sellable-agent/mcp-root/node_modules/@sellable/mcp', mcpNodeModulesRoot: '/opt/sellable-agent/mcp-root/node_modules', hermesSourceRoot: '/opt/hermes', ownerUid: 0, ownerGid: 0 }); if (!built.closureDigest) throw new Error('runtime closure rejected');" \
28
- && node --input-type=module -e "import { chmodSync, writeFileSync } from 'node:fs'; const release = { installerPackage: '@sellable/install@0.1.363', installerIntegrity: process.argv[1], mcpPackage: '@sellable/mcp@0.1.620', mcpIntegrity: process.argv[2] }; writeFileSync('/opt/sellable-agent/release.json', JSON.stringify(release) + '\\n', { mode: 0o444 }); chmodSync('/opt/sellable-agent/release.json', 0o444);" "${INSTALLER_INTEGRITY}" "${MCP_INTEGRITY}" \
28
+ && node --input-type=module -e "import { chmodSync, writeFileSync } from 'node:fs'; const release = { installerPackage: '@sellable/install@0.1.365-phase111.20260720162325', installerIntegrity: process.argv[1], mcpPackage: '@sellable/mcp@0.1.626-phase111.20260720143315', mcpIntegrity: process.argv[2] }; writeFileSync('/opt/sellable-agent/release.json', JSON.stringify(release) + '\\n', { mode: 0o444 }); chmodSync('/opt/sellable-agent/release.json', 0o444);" "${INSTALLER_INTEGRITY}" "${MCP_INTEGRITY}" \
29
29
  && npm cache clean --force
30
30
 
31
31
  COPY entrypoint.sh /usr/local/bin/sellable-agent-container-entrypoint
32
- RUN test -x /usr/local/bin/node \
33
- && test ! -e /usr/bin/node \
34
- && ln -s /usr/local/bin/node /usr/bin/node \
35
- && test "$(readlink -f /usr/bin/node)" = "/usr/local/bin/node" \
36
- && chmod 0555 /usr/local/bin/sellable-agent-container-entrypoint
32
+ RUN chmod 0555 /usr/local/bin/sellable-agent-container-entrypoint
37
33
 
38
34
  HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
39
35
  CMD /command/s6-svstat /etc/services.d/sellable-agent-host-worker | grep -q '^up' || exit 1
@@ -2,12 +2,12 @@
2
2
 
3
3
  This image is a dedicated Agent worker/runtime boundary beside an existing Hermes dashboard. It never replaces or reconfigures the dashboard container.
4
4
 
5
- Build from this directory after `@sellable/install@0.1.363` is published:
5
+ Build from this directory after `@sellable/install@0.1.365-phase111.20260720162325` is published:
6
6
 
7
7
  ```sh
8
8
  docker build --platform linux/amd64 \
9
9
  --build-arg INSTALLER_INTEGRITY='sha512-…' \
10
- --tag sellable-agent-host:0.1.363 .
10
+ --tag sellable-agent-host:0.1.365-phase111.20260720162325 .
11
11
  ```
12
12
 
13
13
  Create a private `bootstrap/` directory owned by root with mode `0700`. It must contain `bootstrap.json`, `provider-auth`, and `slack-app`, each owned by root with mode `0600`. `bootstrap.json` contains only non-secret identities, hashes, package integrities, and the control-plane URL. The other two files contain the provider JSON and global Slack app token whose hashes are pinned by `bootstrap.json`.
@@ -1,15 +1,11 @@
1
1
  #!/bin/sh
2
2
  set -eu
3
3
 
4
- PATH="/command:${PATH}"
5
- export PATH
6
-
7
4
  BOOTSTRAP_CONFIG=/var/lib/sellable-agent-bootstrap/bootstrap.json
8
5
  BOOTSTRAP_BIN=/opt/sellable-agent/install-root/node_modules/.bin/sellable-agent-host-bootstrap
9
6
 
10
7
  test "$(id -u)" = "0"
11
8
  test "$(id -g)" = "0"
12
- install -d -m 0755 -o root -g root /run/s6/container_environment
13
9
  test -x "${BOOTSTRAP_BIN}"
14
10
  test -f "${BOOTSTRAP_CONFIG}"
15
11
 
@@ -28,7 +28,7 @@ import {
28
28
 
29
29
  const MANIFEST_VERSION = "sellable-agent-external-runtime-closure/v1";
30
30
  const MCP_PACKAGE = "@sellable/mcp";
31
- const MCP_VERSION = "0.1.620";
31
+ const MCP_VERSION = "0.1.626-phase111.20260720143315";
32
32
  const SAFE_COMPONENT = /^[A-Za-z0-9@._+-]+$/;
33
33
  const HERMES_EXCLUDES = new Set([".git", ".playwright", "node_modules"]);
34
34
 
@@ -38,8 +38,8 @@ import { installAgentHostServices } from "./service-installer.mjs";
38
38
  const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
39
39
  const VERSION = "sellable-agent-host-bootstrap/v1";
40
40
  const RECEIPT_VERSION = "sellable-agent-host-registration/v1";
41
- const INSTALLER_PACKAGE = "@sellable/install@0.1.363";
42
- const MCP_PACKAGE = "@sellable/mcp@0.1.620";
41
+ const INSTALLER_PACKAGE = "@sellable/install@0.1.365-phase111.20260720162325";
42
+ const MCP_PACKAGE = "@sellable/mcp@0.1.626-phase111.20260720143315";
43
43
  const HERMES_VERSION = "0.18.0";
44
44
  const SHA256 = /^[a-f0-9]{64}$/;
45
45
  const SHA256_16 = /^[a-f0-9]{16}$/;
@@ -957,8 +957,8 @@ export function compileClaimToProfileDesired(activeClaim, config) {
957
957
  ]) ||
958
958
  !exactObject(runtimeService, ["principalId", "policyHash", "credentialGeneration", "credentialFingerprint"]) ||
959
959
  pinned.hermesCli !== "hermes" || pinned.hermesVersion !== "0.18.0" ||
960
- pinned.installerPackage !== "@sellable/install@0.1.363" ||
961
- pinned.mcpPackage !== "@sellable/mcp@0.1.620" ||
960
+ pinned.installerPackage !== "@sellable/install@0.1.365-phase111.20260720162325" ||
961
+ pinned.mcpPackage !== "@sellable/mcp@0.1.626-phase111.20260720143315" ||
962
962
  !Array.isArray(policy.toolInclude) || policy.toolInclude.length === 0 ||
963
963
  !Number.isSafeInteger(slack.generation) || slack.generation < 1 ||
964
964
  !/^A[A-Z0-9]{8,20}$/.test(slack.appId ?? "") ||
@@ -131,8 +131,8 @@ function validateDesired(desired) {
131
131
  !Number.isSafeInteger(desired.slackGeneration) || desired.slackGeneration < 1 ||
132
132
  !Number.isSafeInteger(desired.serviceCredentialGeneration) || desired.serviceCredentialGeneration < 1 ||
133
133
  desired.hermesCli !== "hermes" || desired.hermesVersion !== "0.18.0" ||
134
- desired.installerPackage !== "@sellable/install@0.1.363" ||
135
- desired.mcpPackage !== "@sellable/mcp@0.1.620" ||
134
+ desired.installerPackage !== "@sellable/install@0.1.365-phase111.20260720162325" ||
135
+ desired.mcpPackage !== "@sellable/mcp@0.1.626-phase111.20260720143315" ||
136
136
  !Array.isArray(desired.toolInclude) || desired.toolInclude.length === 0 ||
137
137
  desired.toolInclude.some((tool) => !/^[a-z][a-z0-9_]{0,127}$/.test(tool)) ||
138
138
  new Set(desired.toolInclude).size !== desired.toolInclude.length ||
@@ -19,8 +19,8 @@ import { fileURLToPath } from "node:url";
19
19
  import { deriveContainedProfileId } from "./profile-materializer.mjs";
20
20
 
21
21
  export const PROVISIONING_ACTION = "PROVISION_HERMES_PROFILE";
22
- export const PINNED_INSTALL_PACKAGE = "@sellable/install@0.1.363";
23
- export const PINNED_MCP_PACKAGE = "@sellable/mcp@0.1.620";
22
+ export const PINNED_INSTALL_PACKAGE = "@sellable/install@0.1.365-phase111.20260720162325";
23
+ export const PINNED_MCP_PACKAGE = "@sellable/mcp@0.1.626-phase111.20260720143315";
24
24
 
25
25
  export function deriveAgentProfileId(workspaceId, agentId, hostId) {
26
26
  return deriveContainedProfileId(workspaceId, agentId, hostId);
@@ -576,7 +576,7 @@ function inspectProvisionedProfile({
576
576
  !matchesReadback(manifest, request) ||
577
577
  manifest.serviceCredentialReference !== serviceCredentialReference ||
578
578
  manifest.installerPackage !== PINNED_INSTALL_PACKAGE ||
579
- manifest.installerVersion !== "0.1.363" ||
579
+ manifest.installerVersion !== "0.1.365-phase111.20260720162325" ||
580
580
  manifest.mcpPackage !== PINNED_MCP_PACKAGE ||
581
581
  manifest.credentialKeyVersion !== credentialKeyVersion ||
582
582
  !/^[a-f0-9]{16}$/.test(manifest.credentialFingerprint) ||
@@ -781,7 +781,7 @@ function successReceipt({
781
781
  subject: `agent-profile:${observed.profileId}`,
782
782
  cli: {
783
783
  installPackage: PINNED_INSTALL_PACKAGE,
784
- installVersion: "0.1.363",
784
+ installVersion: "0.1.365-phase111.20260720162325",
785
785
  mcpPackage: PINNED_MCP_PACKAGE,
786
786
  command: "hermes profile bootstrap",
787
787
  },
@@ -821,7 +821,7 @@ function failure(code, stages, extra = {}, request = null) {
821
821
  subject: safeIdentity ? `agent-profile:${safeIdentity}` : "agent-profile:unbound",
822
822
  cli: {
823
823
  installPackage: PINNED_INSTALL_PACKAGE,
824
- installVersion: "0.1.363",
824
+ installVersion: "0.1.365-phase111.20260720162325",
825
825
  mcpPackage: PINNED_MCP_PACKAGE,
826
826
  command: "hermes profile bootstrap",
827
827
  },
@@ -305,7 +305,7 @@ function configurationFiles(input, checked) {
305
305
  ].join("\n");
306
306
  return [
307
307
  ["/etc/sellable-agent/egress-proxy.json", `${JSON.stringify(checked.proxyConfig, null, 2)}\n`, 0o444],
308
- ["/etc/sellable-agent/worker.json", `${JSON.stringify(input.workerConfig, null, 2)}\n`, 0o444],
308
+ ["/etc/sellable-agent/worker.json", `${JSON.stringify(input.workerConfig, null, 2)}\n`, 0o600],
309
309
  ["/etc/sellable-agent/runtime-helper.json", `${JSON.stringify(input.helperConfig, null, 2)}\n`, 0o600],
310
310
  ["/etc/sudoers.d/sellable-agent-runtime-helper", sudoers, 0o440],
311
311
  ].map(([logicalPath, bytes, mode]) => ({ logicalPath, bytes, mode }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/install",
3
- "version": "0.1.363",
3
+ "version": "0.1.365-phase111.20260720162325",
4
4
  "type": "module",
5
5
  "description": "One-command installer for Sellable MCP in Claude Code, Codex, and Hermes",
6
6
  "bin": {
@@ -17,3 +17,8 @@ Use `refill-sends-v2` instead:
17
17
  ```text
18
18
  get_subskill_prompt({ subskillName: "refill-sends-v2" })
19
19
  ```
20
+
21
+ The alias must not strip exact `targetConfig`, server `runHandle`,
22
+ `reportingContext`, canonical `refill_reporting.v2`, or an explicit
23
+ `messageTemplateRevision`. It never infers revision authority from
24
+ `forceRerun`.
@@ -52,9 +52,35 @@ refill_sends_v2({ workspaceId, dryRun:true, intent:"auto", senderIds? })
52
52
  To resume an in-progress run, pass the handle back exactly:
53
53
 
54
54
  ```text
55
- refill_sends_v2({ workspaceId, runId, fence })
55
+ refill_sends_v2({ workspaceId, runHandle:{runId,fence}, targetConfig, reportingContext, messageTemplateRevision? })
56
56
  ```
57
57
 
58
+ `targetConfig` and the server-issued `runHandle` are separate authorities. Keep
59
+ both byte-for-byte through every continuation and takeover; never fold the
60
+ rotating fence into target configuration.
61
+
62
+ An approved copy change is reachable only through an explicit
63
+ `messageTemplateRevision` object with `version:1`, `source:"user_approved"`,
64
+ the approved `templateMarkdown`, `messageTemplateRevision` identity,
65
+ `priorTemplateAuthorityDigest`, `nextTemplateAuthorityDigest`, 1–500 exact
66
+ `cohortRowIds`, and `requestId`, `effectId`, and `revisionOperationId`. Pass it
67
+ with the exact `targetConfig` and `reportingContext`; the command obtains or
68
+ resumes the server run handle and lets the planner name
69
+ `revise_message_template_and_rerun`. Missing or partial input means no revision.
70
+ `forceRerun:true` on a queue operation never authorizes or implies a template
71
+ change.
72
+
73
+ After a committed and dispatched revision receipt, continue only with the
74
+ planner-provided revision-derived `approve_messages` action. It must preserve
75
+ the exact revision cohort as `rowSelector:{type:"rowIds",rowIds:[...]}` plus
76
+ `approvalMode:"approve"`, the compiled readiness identity, and the canonical
77
+ `reportingContext`, even when the ordinary sender lane is ineligible. Do not
78
+ replace it with `queue_campaign_cells` on Approved cells; that path cannot mint
79
+ the authoritative approval/lane-scope receipt. Require `laneScope` in the
80
+ bounded preparation receipt before the lane is considered approved. A
81
+ disconnected or zero-capacity sender still blocks scheduling and does not
82
+ authorize reconnecting, campaign start, or direct send.
83
+
58
84
  If a stale handle loses the lease, the tool reports the holder status and the
59
85
  approximately 10 minute lockout window. Reinvoke with the current handle or wait
60
86
  for lease expiry; never guess a fence.
@@ -85,6 +111,10 @@ The run may execute only packet-named bounded work:
85
111
  when the campaign is in the packet's pinned lane chain;
86
112
  - refresh paid InMail credit facts during bootstrap and once per scheduler-wait
87
113
  entry when the packet requires it.
114
+ - apply `revise_message_template_and_rerun` only when the packet contains the
115
+ complete explicit user-approved revision envelope and exact fenced target.
116
+ - after that revision commits, apply only its exact-row, readiness-bound
117
+ `approve_messages` continuation and require the returned lane-scope receipt.
88
118
 
89
119
  The loop records planned -> did -> outcome before and after every foreign
90
120
  mutation. It refuses stale packets by fingerprint and validates packet
@@ -168,5 +198,10 @@ reason or resume handle, lane source, lane chain, chosen label with token
168
198
  secondary, plan revision, journal path, blocked continuation packets, and
169
199
  firstFailing checklist when present.
170
200
 
201
+ Preserve `refill_reporting.v2` exactly in progress, continuation, replay, and
202
+ terminal results. Do not recompute its semantic counts or drop live-preparation
203
+ epoch, snapshot sequence, reset reason, watermark, blockers, compatibility, or
204
+ terminal fields. Never surface raw prospect or message-copy fields.
205
+
171
206
  Real-run journals live under `~/.sellable/refill/runs` by default and append an
172
207
  index line. Dry runs include the dry marker and do not create run records.
@@ -93,7 +93,7 @@ Accepted invocation flags in the same user request:
93
93
  When the host can call typed MCP tools, start with:
94
94
 
95
95
  ```text
96
- refill_sends({ yolo?: boolean, executionMode?: "manual" | "scheduled" | "yolo", requireWorkspace?: boolean, workspaceId?: string, senders?: string[], senderIds?: string[], senderNames?: string[], actionTypes?: ("send_invite" | "send_inmail_closed")[], horizonSendDays?: number, untilDate?: "YYYY-MM-DD", targetDate?: "YYYY-MM-DD", runId?: string, fence?: number })
96
+ refill_sends({ yolo?: boolean, executionMode?: "manual" | "scheduled" | "yolo", requireWorkspace?: boolean, workspaceId?: string, senders?: string[], senderIds?: string[], senderNames?: string[], actionTypes?: ("send_invite" | "send_inmail_closed")[], horizonSendDays?: number, untilDate?: "YYYY-MM-DD", targetDate?: "YYYY-MM-DD", runHandle?: RefillRunHandleV1, targetConfig?: RefillTargetConfigV1, reportingContext?: RefillReportingContextV2, messageTemplateRevision?: MessageTemplateRevisionV1 })
97
97
  ```
98
98
 
99
99
  That command helper normalizes arguments and returns the execution contract. In
@@ -104,6 +104,49 @@ host must immediately call `refill_sends` again with the original full scope
104
104
  plus the exact returned `runId` and `fence`. Do this automatically inside the
105
105
  same user command until terminal projected coverage or a concrete blocker; do
106
106
  not ask the user to type `continue` and do not start a second run.
107
+
108
+ `targetConfig` and the server-issued `runHandle` are separate values and must
109
+ survive each continuation unchanged except for a server-issued takeover fence.
110
+ Only a literal `messageTemplateRevision` with `source:"user_approved"`, the
111
+ approved `templateMarkdown`, prior/new authority digests, 1–500 exact cohort
112
+ row IDs, and request/effect/revision-operation IDs can authorize
113
+ `revise_message_template_and_rerun`. No envelope means no revision. A
114
+ `forceRerun:true` queue call cannot infer or authorize template mutation.
115
+ When the user supplied a complete revision except for the current prior digest,
116
+ acquire the exact run first and reread with its unchanged `targetConfig` and
117
+ `runHandle`. Use only `reportingContext.source.templateAuthorityDigest` from
118
+ that fenced reread as `priorTemplateAuthorityDigest`, then immediately continue
119
+ the same run with the complete literal revision—even when the ordinary refill
120
+ queue is empty or the sender is otherwise ineligible. An empty ordinary queue
121
+ must not suppress an explicit revision. If the fenced reread returns no digest,
122
+ stop with `template_authority_missing` or the returned
123
+ `templateAuthorityBlocker`; never guess the digest.
124
+ After the revision receipt is committed and dispatched, the next mutation must
125
+ be the planner-provided revision-derived `approve_messages` action. Preserve
126
+ its exact `rowSelector:{type:"rowIds",rowIds:[...]}` cohort,
127
+ `approvalMode:"approve"`, compiled readiness authority, and
128
+ `reportingContext`, even if the ordinary sender lane is ineligible. Never use
129
+ `queue_campaign_cells` to flip Approved cells: that path cannot produce the
130
+ authoritative approval/lane-scope receipt. Require the bounded preparation
131
+ receipt's `laneScope` before considering the selected first-touch lane
132
+ approved. Zero sender capacity remains a scheduler blocker and never authorizes
133
+ reconnection, campaign start, or direct send.
134
+
135
+ Before that approval mutation, perform an exact authoritative copy audit with
136
+ `select_campaign_cells({ columnRole:"generateMessage",
137
+ rowSelector:{type:"rowIds",rowIds:[...]},
138
+ includeGeneratedMessageReview:true })` over the complete revision cohort in
139
+ batches of at most 20. Require every
140
+ `authoritativeGeneratedMessageReview.complete:true`, every row status to be
141
+ completed-like, the exact user-approved rendered subject/body, the expected
142
+ `templateAuthorityDigest`, and `matchesCurrentTemplateAuthority:true`. This
143
+ projection is the only MCP copy-review source of truth. Never use formula
144
+ Message/Subject columns, campaign message previews, table previews, or CSV
145
+ exports to validate revised Generate Message results. If any row is missing,
146
+ non-current, or text/digest mismatched, stop before approval and report
147
+ `regenerated_copy_integrity_failed`; never regenerate again implicitly or
148
+ approve the cohort on counts/revision hashes alone.
149
+
107
150
  Safe primitives are paid-credit refresh,
108
151
  existing-row message preparation, generated-message approval, receipt-proven
109
152
  same-source row copy, one bounded Signal Discovery search derived only from
@@ -567,6 +610,11 @@ scheduler wait loop. Do not finish the refill goal, mark it complete, or mark it
567
610
  blocked only because it is still `awaiting_scheduler_after_ready_buffer`; keep
568
611
  waiting unless Christian stops the run or the host cannot continue.
569
612
 
613
+ Carry the canonical `refill_reporting.v2` DTO through progress, continuation,
614
+ replay, and terminal reporting without recomputing counts or losing live epoch,
615
+ snapshot sequence, reset reason, watermark, blockers, compatibility, or
616
+ terminal outcome. Redact raw copy and prospect fields.
617
+
570
618
  Future scheduled coverage and already sent actions are distinct; only
571
619
  scheduler-owned future scheduled actions count as scheduled coverage. For
572
620
  multiple target dates, finish D1 execution and the full D1 reread before