@remits/remits-cli 0.1.116 → 0.1.117

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/index.js CHANGED
@@ -544,6 +544,8 @@ function localStatePaths(cwd) {
544
544
  toolsDir: path.join(base, 'tools'),
545
545
  sessionsDir: path.join(base, 'sessions'),
546
546
  toolResponsesDir: path.join(base, 'tool-responses'),
547
+ verificationDir: path.join(base, 'verification'),
548
+ activeVerificationFile: path.join(base, 'verification', 'active'),
547
549
  currentSessionFile: path.join(base, 'current-session.txt'),
548
550
  workspaceFile: path.join(base, 'workspace')
549
551
  };
@@ -649,6 +651,7 @@ function ensureLocalState(cwd) {
649
651
  ensureDir(paths.toolsDir);
650
652
  ensureDir(paths.sessionsDir);
651
653
  ensureDir(paths.toolResponsesDir);
654
+ ensureDir(paths.verificationDir);
652
655
  return paths;
653
656
  }
654
657
 
@@ -755,6 +758,196 @@ function readToolResponseFile(cwd, callId) {
755
758
  }
756
759
  }
757
760
 
761
+ function verificationPaths(cwd, envelopeId) {
762
+ const paths = ensureLocalState(cwd);
763
+ const base = envelopeId ? path.join(paths.verificationDir, String(envelopeId)) : paths.verificationDir;
764
+ return {
765
+ root: paths.verificationDir,
766
+ activeFile: paths.activeVerificationFile,
767
+ base,
768
+ envelopeFile: path.join(base, 'envelope.json'),
769
+ manifestFile: path.join(base, 'manifest.json'),
770
+ packetsDir: path.join(base, 'packets')
771
+ };
772
+ }
773
+
774
+ function readActiveVerificationEnvelope(cwd) {
775
+ const file = verificationPaths(cwd).activeFile;
776
+ try {
777
+ if (!fs.existsSync(file)) return null;
778
+ const id = fs.readFileSync(file, 'utf8').trim();
779
+ return id || null;
780
+ } catch (_) {
781
+ return null;
782
+ }
783
+ }
784
+
785
+ function writeActiveVerificationEnvelope(cwd, envelopeId) {
786
+ const paths = verificationPaths(cwd);
787
+ if (!envelopeId) {
788
+ try { if (fs.existsSync(paths.activeFile)) fs.unlinkSync(paths.activeFile); } catch (_) { /* best-effort */ }
789
+ return null;
790
+ }
791
+ atomicWriteFile(paths.activeFile, String(envelopeId) + '\n');
792
+ return String(envelopeId);
793
+ }
794
+
795
+ function verificationEnvelopeIdForCommand(cwd, flags = {}) {
796
+ if (flagEnabled(flags['no-verify-envelope']) || flagEnabled(flags.noVerifyEnvelope)) return null;
797
+ const explicit = flags['verify-envelope'] || flags.verifyEnvelope || flags.envelope;
798
+ if (explicit !== undefined && explicit !== null && explicit !== true) return String(explicit).trim() || null;
799
+ return readActiveVerificationEnvelope(cwd);
800
+ }
801
+
802
+ function writeLocalVerificationEnvelope(cwd, envelope) {
803
+ if (!envelope || !envelope.envelopeId) return null;
804
+ const paths = verificationPaths(cwd, envelope.envelopeId);
805
+ ensureDir(paths.base);
806
+ ensureDir(paths.packetsDir);
807
+ atomicWriteFile(paths.envelopeFile, JSON.stringify(envelope, null, 2));
808
+ if (envelope.acceptance) {
809
+ atomicWriteFile(paths.manifestFile, JSON.stringify(envelope.acceptance, null, 2));
810
+ }
811
+ (envelope.packets || []).forEach((packet) => writeLocalVerificationPacket(cwd, envelope.envelopeId, packet));
812
+ return paths.envelopeFile;
813
+ }
814
+
815
+ function readLocalVerificationEnvelope(cwd, envelopeId) {
816
+ const paths = verificationPaths(cwd, envelopeId);
817
+ if (!fs.existsSync(paths.envelopeFile)) return null;
818
+ try {
819
+ return JSON.parse(fs.readFileSync(paths.envelopeFile, 'utf8'));
820
+ } catch (_) {
821
+ return null;
822
+ }
823
+ }
824
+
825
+ function writeLocalVerificationPacket(cwd, envelopeId, packet) {
826
+ if (!envelopeId || !packet) return null;
827
+ const paths = verificationPaths(cwd, envelopeId);
828
+ ensureDir(paths.packetsDir);
829
+ const packetId = packet.packetId || ('local-' + crypto.randomUUID());
830
+ const file = path.join(paths.packetsDir, packetId + '.json');
831
+ atomicWriteFile(file, JSON.stringify(Object.assign({}, packet, { packetId }), null, 2));
832
+ return file;
833
+ }
834
+
835
+ function parseManifestFile(file) {
836
+ const resolved = path.resolve(process.cwd(), String(file));
837
+ const text = fs.readFileSync(resolved, 'utf8');
838
+ if (resolved.endsWith('.yml') || resolved.endsWith('.yaml')) {
839
+ return YAML.parse(text);
840
+ }
841
+ return JSON.parse(text);
842
+ }
843
+
844
+ function sha256File(file) {
845
+ const resolved = path.resolve(process.cwd(), String(file));
846
+ const hash = crypto.createHash('sha256');
847
+ hash.update(fs.readFileSync(resolved));
848
+ const stat = fs.statSync(resolved);
849
+ return { path: resolved, sha256: hash.digest('hex'), size: stat.size };
850
+ }
851
+
852
+ function safeGitValue(cwd, command) {
853
+ try {
854
+ return runGit(cwd, command);
855
+ } catch (_) {
856
+ return null;
857
+ }
858
+ }
859
+
860
+ function collectVerificationSource(cwd, flags = {}) {
861
+ const branchName = flags.branch || currentBranch(cwd);
862
+ return {
863
+ workingDirectory: cwd,
864
+ gitBranch: branchName,
865
+ gitHead: safeGitValue(cwd, 'git rev-parse HEAD'),
866
+ originHead: safeGitValue(cwd, 'git rev-parse ' + shellQuote('origin/' + branchName)),
867
+ mergeBase: safeGitValue(cwd, 'git merge-base HEAD ' + shellQuote('origin/' + branchName)),
868
+ workspace: resolveWorkspace(cwd, flags),
869
+ workspaceSource: workspaceSource(cwd, flags),
870
+ stagingLane: branchName + ':' + (resolveWorkspace(cwd, flags) || 'shared'),
871
+ sourceLayer: 'unknown'
872
+ };
873
+ }
874
+
875
+ function collectVerificationAccount(cwd, accountId, baseUrl, dataMode) {
876
+ const info = loadAccountInfo(cwd) || {};
877
+ const resolution = info.resolution || info.repoContext || {};
878
+ return {
879
+ repoAccountId: Number(accountId || accountIdFromAccountInfo(info)) || null,
880
+ executionAccountId: resolution.executionAccountId || resolution.accountId || accountId || null,
881
+ addressedAccountId: resolution.addressedAccountId || resolution.accountId || accountId || null,
882
+ componentOwnerAccountId: resolution.componentOwnerAccountId || resolution.ownerAccountId || null,
883
+ resolvedDatabaseName: resolution.resolvedDatabaseName || resolution.databaseName || null,
884
+ host: resolution.resolvedDomainName || resolution.host || normalizeBaseUrl(baseUrl),
885
+ dataMode,
886
+ dataModeSource: 'cli',
887
+ accountRole: resolution.role || null,
888
+ accountType: resolution.type || null,
889
+ testAccount: resolution.testAccount === true || info.testAccount === true
890
+ };
891
+ }
892
+
893
+ async function postVerificationCommand(api, cwd, session, accountId, command, payload) {
894
+ const response = await loggedPost(api, cwd, '/cli/verification', Object.assign({
895
+ token: session.token,
896
+ accountId,
897
+ command
898
+ }, payload || {})).then((r) => r.data);
899
+ if (!response.success) {
900
+ throw new Error(response.message || 'Verification command failed');
901
+ }
902
+ return response;
903
+ }
904
+
905
+ async function appendVerificationPacket(api, cwd, session, accountId, flags, packet, options = {}) {
906
+ const envelopeId = verificationEnvelopeIdForCommand(cwd, flags);
907
+ if (!envelopeId) return null;
908
+ const finalPacket = Object.assign({
909
+ packetId: crypto.randomUUID(),
910
+ createdAt: new Date().toISOString(),
911
+ success: true
912
+ }, packet || {});
913
+ finalPacket.envelopeId = envelopeId;
914
+ writeLocalVerificationPacket(cwd, envelopeId, finalPacket);
915
+ try {
916
+ const response = await postVerificationCommand(api, cwd, session, accountId, 'packet', {
917
+ envelopeId,
918
+ packet: finalPacket
919
+ });
920
+ if (response.packet) writeLocalVerificationPacket(cwd, envelopeId, response.packet);
921
+ if (response.envelope) writeLocalVerificationEnvelope(cwd, response.envelope);
922
+ if (!options.quiet) console.log('Verification envelope:', envelopeId, '(packet ' + finalPacket.type + ' attached)');
923
+ return response;
924
+ } catch (err) {
925
+ if (!options.quiet) {
926
+ console.error('Verification evidence captured locally but not attached to the platform envelope:', describeError(err));
927
+ }
928
+ return { localOnly: true, envelopeId, packet: finalPacket };
929
+ }
930
+ }
931
+
932
+ function buildCommandWorld(response, fallback = {}) {
933
+ const source = (response && response.staging) || {};
934
+ const resolution = (response && response.resolution) || {};
935
+ return {
936
+ accountId: response && (response.accountId || response.ownerAccountId) || fallback.accountId,
937
+ executionAccountId: response && (response.executionAccountId || response.accountId) || resolution.executionAccountId || fallback.executionAccountId,
938
+ addressedAccountId: resolution.addressedAccountId || fallback.addressedAccountId,
939
+ host: fallback.host,
940
+ dataMode: response && response.dataMode || fallback.dataMode,
941
+ dataModeSource: response && response.dataModeSource || fallback.dataModeSource,
942
+ branchName: response && response.branchName || fallback.branchName,
943
+ workspace: response && response.workspace !== undefined ? response.workspace : fallback.workspace,
944
+ stagingLane: response && response.stagingLane || source.stagingLane || fallback.stagingLane,
945
+ componentBranch: resolution.componentBranch || response && response.variantBranch || fallback.componentBranch,
946
+ variantId: resolution.variantId || fallback.variantId,
947
+ sourceLayer: fallback.sourceLayer || (source.testComponentSource === 'staged' ? 'staged' : undefined)
948
+ };
949
+ }
950
+
758
951
  function normalizeName(name) {
759
952
  return name.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/\s+/g, ' ').trim();
760
953
  }
@@ -2552,6 +2745,24 @@ async function pushComponentsCommand(flags) {
2552
2745
  response.requestedStageMode = stageMode;
2553
2746
  response.unrepresentableChanges = unstageable;
2554
2747
 
2748
+ await appendVerificationPacket(api, cwd, session, accountId, flags, {
2749
+ type: 'stage',
2750
+ success: response.success !== false,
2751
+ claim: 'Components staged for verification',
2752
+ world: buildCommandWorld(response, { accountId, dataMode, branchName, workspace, host: normalizeBaseUrl(baseUrl), sourceLayer: 'staged' }),
2753
+ revision: collectVerificationSource(cwd, flags),
2754
+ assertions: [
2755
+ { category: 'stage', stageMode },
2756
+ { category: 'staging_lane', value: response.stagingLane }
2757
+ ],
2758
+ evidenceCategories: ['stage', 'staging_lane'],
2759
+ rawRefs: { command: 'components stage' },
2760
+ stage: response.stage,
2761
+ laneSummary: response.laneSummary,
2762
+ changedFromWorkingTree: response.changedFromWorkingTree,
2763
+ unrepresentableChanges: response.unrepresentableChanges
2764
+ }, { quiet: flagEnabled(flags.json) });
2765
+
2555
2766
  if (flagEnabled(flags.json)) {
2556
2767
  console.log(JSON.stringify(response, null, 2));
2557
2768
  return response;
@@ -3034,6 +3245,17 @@ async function statusComponentsCommand(flags) {
3034
3245
  throw new Error(response.message || 'Staging status failed');
3035
3246
  }
3036
3247
 
3248
+ await appendVerificationPacket(api, cwd, session, accountId, flags, {
3249
+ type: 'component_status',
3250
+ success: true,
3251
+ claim: 'Component status observed',
3252
+ world: buildCommandWorld(response, { accountId, dataMode, branchName, workspace, host: normalizeBaseUrl(baseUrl) }),
3253
+ revision: collectVerificationSource(cwd, flags),
3254
+ evidenceCategories: ['component_status'],
3255
+ rawRefs: { command: 'components status' },
3256
+ componentStatus: response
3257
+ }, { quiet: flagEnabled(flags.json) });
3258
+
3037
3259
  if (flagEnabled(flags.json)) {
3038
3260
  console.log(JSON.stringify(response, null, 2));
3039
3261
  return response;
@@ -3241,6 +3463,23 @@ async function syncComponentsCommand(rawFlags) {
3241
3463
  const gate = preflightGate || evaluateSyncGates(response, gateFlags, changedFromWorkingTree, cwd);
3242
3464
  summary.gates = gate.checks;
3243
3465
 
3466
+ await appendVerificationPacket(api, cwd, session, accountId, flags, {
3467
+ type: response.sync && response.sync.dryRun ? 'sync_dry_run' : 'sync_mutation',
3468
+ success: response.success !== false,
3469
+ claim: response.sync && response.sync.dryRun ? 'Component sync dry-run observed' : 'Component sync completed',
3470
+ world: buildCommandWorld(response, { accountId, dataMode, branchName, workspace, host: normalizeBaseUrl(baseUrl), sourceLayer: response.sync && response.sync.mode === 'variant' ? 'variant' : 'trunk' }),
3471
+ revision: Object.assign(collectVerificationSource(cwd, flags), {
3472
+ platformSyncedSha: response.sync && (response.sync.postSyncSha || response.sync.branchHeadSha)
3473
+ }),
3474
+ evidenceCategories: [response.sync && response.sync.dryRun ? 'sync_dry_run' : 'sync_mutation'],
3475
+ assertions: [
3476
+ { category: response.sync && response.sync.dryRun ? 'sync_dry_run' : 'sync_mutation', summary }
3477
+ ],
3478
+ rawRefs: { command: 'components sync' },
3479
+ sync: response.sync,
3480
+ summary
3481
+ }, { quiet: flagEnabled(flags.json) });
3482
+
3244
3483
  if (flagEnabled(flags.json)) {
3245
3484
  console.log(JSON.stringify(flagEnabled(flags.summary) ? summary : response, null, 2));
3246
3485
  failOnSyncGate(gate);
@@ -4363,6 +4602,30 @@ async function testCommand(flags) {
4363
4602
  } else if (status.result && status.result.failed > 0) {
4364
4603
  process.exitCode = 1;
4365
4604
  }
4605
+
4606
+ await appendVerificationPacket(api, cwd, session, accountId, flags, {
4607
+ type: 'test_run',
4608
+ success: status.status === 'completed' && !(status.result && status.result.failed > 0) && !unmatched.length,
4609
+ claim: 'Test run ' + String(testRef),
4610
+ world: buildCommandWorld(status, { accountId, dataMode: status.dataMode || dataMode, dataModeSource, branchName, workspace, host: normalizeBaseUrl(baseUrl), sourceLayer: status.staging && status.staging.testComponentSource }),
4611
+ revision: Object.assign(collectVerificationSource(cwd, flags), {
4612
+ compileSignatures: status.staging && status.staging.testComponentSignature ? [status.staging.testComponentSignature] : []
4613
+ }),
4614
+ test: {
4615
+ taskId: start.taskId,
4616
+ testId: status.test && status.test.id,
4617
+ testName: status.test && status.test.name,
4618
+ selectedCases: names,
4619
+ passed: status.result && status.result.passed,
4620
+ failed: status.result && status.result.failed,
4621
+ dataModeSource: status.dataModeSource || dataModeSource,
4622
+ unmatchedTestNames: unmatched
4623
+ },
4624
+ evidenceCategories: ['test_run'].concat(names.map((name) => 'test_run.' + name)),
4625
+ limitations: unmatched.length ? ['One or more requested test case selectors matched no case.'] : [],
4626
+ rawRefs: { testStatusKey: start.taskId },
4627
+ status
4628
+ });
4366
4629
  }
4367
4630
 
4368
4631
  async function tokenCommand(flags) {
@@ -4451,6 +4714,21 @@ async function tokenCommand(flags) {
4451
4714
 
4452
4715
  printSessionResolutionWarning(sessionContext);
4453
4716
  console.log(JSON.stringify(output, null, 2));
4717
+
4718
+ await appendVerificationPacket(api, cwd, session, accountId, flags, {
4719
+ type: 'token_inspect',
4720
+ success: true,
4721
+ claim: 'Verification token minted',
4722
+ world: buildCommandWorld(output, { accountId: output.accountId || accountId, dataMode: output.dataMode || dataMode, branchName, workspace: output.workspace, host: normalizeBaseUrl(baseUrl) }),
4723
+ revision: collectVerificationSource(cwd, flags),
4724
+ browser: {
4725
+ tokenKeyHash: output.tokenKey ? sha256(String(output.tokenKey)) : null,
4726
+ urlHash: output.embeddableUrl ? sha256(String(output.embeddableUrl)) : null,
4727
+ liveUrl: flagEnabled(flags['store-url']) ? output.embeddableUrl : undefined
4728
+ },
4729
+ evidenceCategories: ['token_inspect'],
4730
+ token: Object.assign({}, output, { tokenKey: output.tokenKey ? '[redacted]' : undefined, embedTokenKey: output.embedTokenKey ? '[redacted]' : undefined })
4731
+ });
4454
4732
  }
4455
4733
 
4456
4734
  async function tokenInspectCommand(flags) {
@@ -4480,6 +4758,19 @@ async function tokenInspectCommand(flags) {
4480
4758
  printSessionResolutionWarning(sessionContext);
4481
4759
  printResolvedBaseUrl(baseUrl);
4482
4760
  console.log(JSON.stringify(data, null, 2));
4761
+
4762
+ await appendVerificationPacket(api, cwd, session, accountId, flags, {
4763
+ type: 'token_inspect',
4764
+ success: true,
4765
+ claim: 'Token inspected',
4766
+ world: buildCommandWorld(data.safety || {}, { accountId: data.context && data.context.accountId || accountId, dataMode: data.safety && data.safety.dataMode, host: normalizeBaseUrl(baseUrl) }),
4767
+ revision: collectVerificationSource(cwd, flags),
4768
+ browser: {
4769
+ tokenKeyHash: data.token && data.token.tokenKey ? sha256(String(data.token.tokenKey)) : null
4770
+ },
4771
+ evidenceCategories: ['token_inspect'],
4772
+ tokenInspect: data
4773
+ });
4483
4774
  }
4484
4775
 
4485
4776
  async function toolsCommand(flags) {
@@ -4650,6 +4941,17 @@ async function toolCommand(flags) {
4650
4941
  console.log('Tool error:', data.toolMessage || toolResultMessage(data.result));
4651
4942
  }
4652
4943
  if (data.status === 'failed' || polledFailed) process.exitCode = 1;
4944
+ await appendVerificationPacket(api, cwd, session, accountId, flags, {
4945
+ type: 'tool_call',
4946
+ success: data.status !== 'failed' && !polledFailed,
4947
+ claim: 'Tool status ' + requestedCallId,
4948
+ world: buildCommandWorld(data, { accountId: statusAccountId, dataMode: data.dataMode || dataMode, branchName: flags.branch || (stored && stored.branchName) || branchName, host: normalizeBaseUrl(baseUrl) }),
4949
+ revision: collectVerificationSource(cwd, flags),
4950
+ evidenceCategories: ['tool_call'],
4951
+ rawRefs: { toolResponsePath: statusResponse.responseFile, callId: requestedCallId },
4952
+ tool: { callId: requestedCallId, status: data.status, name: stored && stored.name },
4953
+ result: data
4954
+ });
4653
4955
  return;
4654
4956
  }
4655
4957
 
@@ -4732,6 +5034,274 @@ async function toolCommand(flags) {
4732
5034
  process.exitCode = 1;
4733
5035
  }
4734
5036
  }
5037
+
5038
+ await appendVerificationPacket(api, cwd, session, accountId, flags, {
5039
+ type: 'tool_call',
5040
+ success: !toolFailed && (!asyncMode || !waitForAsync || process.exitCode !== 1),
5041
+ claim: 'Tool call ' + String(toolName),
5042
+ world: buildCommandWorld(data, { accountId: data.accountId || accountId, dataMode: data.dataMode || dataMode, branchName: data.branchName || branchName, workspace: resolveWorkspace(cwd, flags), host: normalizeBaseUrl(baseUrl), sourceLayer: data.componentSource }),
5043
+ revision: Object.assign(collectVerificationSource(cwd, flags), {
5044
+ compileSignatures: data.componentSignature ? [data.componentSignature] : []
5045
+ }),
5046
+ evidenceCategories: ['tool_call'],
5047
+ dependencies: {
5048
+ mocks: [],
5049
+ liveHttpCalls: data.liveHttpCalls || []
5050
+ },
5051
+ rawRefs: { toolResponsePath: response.responseFile, callId },
5052
+ tool: { callId, name: String(toolName), async: asyncMode, status: data.status, toolSuccess: data.toolSuccess },
5053
+ result: data
5054
+ });
5055
+ }
5056
+
5057
+ async function verifyCommand(flags, subcommand) {
5058
+ const cwd = process.cwd();
5059
+ ensureLocalState(cwd);
5060
+ const sub = String(subcommand || (flags._ && flags._[1]) || 'status').toLowerCase();
5061
+
5062
+ if (sub === 'current') {
5063
+ const active = readActiveVerificationEnvelope(cwd);
5064
+ if (!active) {
5065
+ console.log('No active verification envelope in this checkout.');
5066
+ return;
5067
+ }
5068
+ console.log('Active verification envelope:', active);
5069
+ const local = readLocalVerificationEnvelope(cwd, active);
5070
+ if (local && local.summary) console.log('Summary:', local.summary);
5071
+ return;
5072
+ }
5073
+
5074
+ if (sub === 'use') {
5075
+ const envelopeId = flags._ && flags._[2] || flags.envelope || flags['verify-envelope'];
5076
+ if (!envelopeId) throw new Error('Usage: remits-cli verify use <envelopeId>');
5077
+ writeActiveVerificationEnvelope(cwd, envelopeId);
5078
+ console.log('Active verification envelope:', envelopeId);
5079
+ return;
5080
+ }
5081
+
5082
+ if (sub === 'clear') {
5083
+ writeActiveVerificationEnvelope(cwd, null);
5084
+ console.log('Verification envelope cleared for this checkout.');
5085
+ return;
5086
+ }
5087
+
5088
+ const sessionContext = resolveSessionContext(cwd, flags);
5089
+ const { session, accountId } = sessionContext;
5090
+ const baseUrl = flags['base-url'] || session.baseUrl || DEFAULT_BASE_URL;
5091
+ const dataMode = resolveDataMode(flags, session);
5092
+ const branchName = flags.branch || currentBranch(cwd);
5093
+ const workspace = resolveWorkspace(cwd, flags);
5094
+ const api = buildAxios(baseUrl, session.token);
5095
+
5096
+ if (sub === 'start') {
5097
+ const summary = flags.summary || flags._ && flags._.slice(2).join(' ');
5098
+ if (!summary) throw new Error('verify start requires --summary "..."');
5099
+ let manifest = {};
5100
+ if (flags.manifest || flags.file) {
5101
+ manifest = parseManifestFile(flags.manifest || flags.file);
5102
+ }
5103
+ let statusResponse = null;
5104
+ try {
5105
+ statusResponse = await loggedPost(api, cwd, '/cli/components', {
5106
+ token: session.token,
5107
+ accountId,
5108
+ branchName,
5109
+ workspace,
5110
+ dataMode,
5111
+ mode: 'status'
5112
+ }).then((r) => r.data);
5113
+ } catch (_) { /* best-effort */ }
5114
+
5115
+ const source = collectVerificationSource(cwd, flags);
5116
+ if (statusResponse) {
5117
+ source.componentBranch = statusResponse.branchContext && statusResponse.branchContext.onTrunk ? 'trunk' : branchName;
5118
+ source.sourceLayer = statusResponse.laneSummary && statusResponse.laneSummary.stagedCount > 0
5119
+ ? 'staged' : (statusResponse.branchContext && statusResponse.branchContext.onTrunk ? 'trunk' : 'variant');
5120
+ source.stagedOverlayHash = statusResponse.laneSummary ? sha256(stableStringify(statusResponse.laneSummary)) : null;
5121
+ }
5122
+
5123
+ const response = await postVerificationCommand(api, cwd, session, accountId, 'start', {
5124
+ summary,
5125
+ ticketId: flags.ticket,
5126
+ account: collectVerificationAccount(cwd, accountId, baseUrl, dataMode),
5127
+ source,
5128
+ manifest
5129
+ });
5130
+ const envelope = response.envelope;
5131
+ writeLocalVerificationEnvelope(cwd, envelope);
5132
+ writeActiveVerificationEnvelope(cwd, envelope.envelopeId);
5133
+ console.log('Verification envelope started:', envelope.envelopeId);
5134
+ console.log('Target:', 'account=' + accountId + ', branch=' + branchName + ', workspace=' + (workspace || 'shared') + ', dataMode=' + dataMode);
5135
+ console.log('Local mirror:', verificationPaths(cwd, envelope.envelopeId).base);
5136
+ return envelope;
5137
+ }
5138
+
5139
+ const envelopeId = flags.envelope || flags['verify-envelope'] || flags.id || readActiveVerificationEnvelope(cwd);
5140
+ if (!envelopeId) {
5141
+ throw new Error('No verification envelope selected. Run `remits-cli verify start --summary "..."` or pass --envelope <id>.');
5142
+ }
5143
+
5144
+ if (sub === 'manifest') {
5145
+ if (flagEnabled(flags.print)) {
5146
+ const local = readLocalVerificationEnvelope(cwd, envelopeId);
5147
+ const response = await postVerificationCommand(api, cwd, session, accountId, 'show', { envelopeId })
5148
+ .catch(() => ({ envelope: local }));
5149
+ const manifest = response.envelope && response.envelope.acceptance;
5150
+ console.log(JSON.stringify(manifest || {}, null, 2));
5151
+ return manifest;
5152
+ }
5153
+ const file = flags.file || flags.manifest;
5154
+ if (!file) throw new Error('verify manifest requires --file <manifest.json> or --print');
5155
+ const manifest = parseManifestFile(file);
5156
+ const response = await postVerificationCommand(api, cwd, session, accountId, 'manifest', { envelopeId, manifest });
5157
+ writeLocalVerificationEnvelope(cwd, response.envelope);
5158
+ console.log('Manifest attached to envelope:', envelopeId);
5159
+ console.log('Required evidence:', (((response.envelope || {}).acceptance || {}).requiredEvidence || []).length);
5160
+ return response.envelope;
5161
+ }
5162
+
5163
+ if (sub === 'show' || sub === 'read' || sub === 'get' || sub === 'packets') {
5164
+ const response = await postVerificationCommand(api, cwd, session, accountId, 'show', { envelopeId });
5165
+ if (response.envelope) writeLocalVerificationEnvelope(cwd, response.envelope);
5166
+ if (flagEnabled(flags.json)) {
5167
+ console.log(JSON.stringify(response.envelope || response, null, 2));
5168
+ return response.envelope || response;
5169
+ }
5170
+ const envelope = response.envelope || {};
5171
+ console.log('Verification envelope:', envelope.envelopeId || envelopeId);
5172
+ if (envelope.summary) console.log('Summary:', envelope.summary);
5173
+ console.log('Status:', envelope.status || 'unknown');
5174
+ const packets = Array.isArray(envelope.packets) ? envelope.packets : [];
5175
+ console.log('Packets:', packets.length);
5176
+ packets.forEach((packet) => {
5177
+ console.log('- ' + (packet.type || 'unknown') + ' ' + (packet.packetId || '') + (packet.success === false ? ' failed' : ''));
5178
+ });
5179
+ return envelope;
5180
+ }
5181
+
5182
+ if (sub === 'attach') {
5183
+ const artifacts = [];
5184
+ let type = 'manual_observation';
5185
+ const label = flags.label || flags.name || 'attached evidence';
5186
+ if (flags.artifact) {
5187
+ type = 'artifact';
5188
+ artifacts.push(Object.assign({ label, kind: 'artifact', source: 'local_path' }, sha256File(flags.artifact)));
5189
+ }
5190
+ if (flags.screenshot) {
5191
+ type = 'artifact';
5192
+ artifacts.push(Object.assign({ label, kind: 'screenshot', source: 'local_path' }, sha256File(flags.screenshot)));
5193
+ }
5194
+ const note = flags.note || flags.message;
5195
+ if (!artifacts.length && !note) {
5196
+ throw new Error('verify attach requires --artifact <path>, --screenshot <path>, or --note "..."');
5197
+ }
5198
+ const packet = {
5199
+ type,
5200
+ success: true,
5201
+ claim: note || label,
5202
+ artifacts,
5203
+ evidenceCategories: artifacts.map((a) => a.kind === 'screenshot' ? 'screenshot' : 'artifact.sha256')
5204
+ };
5205
+ const response = await appendVerificationPacket(api, cwd, session, accountId, { 'verify-envelope': envelopeId }, packet);
5206
+ console.log('Evidence attached:', (response.packet || packet).packetId);
5207
+ return response;
5208
+ }
5209
+
5210
+ if (sub === 'browser-start' || sub === 'browser-step' || sub === 'browser-snapshot') {
5211
+ const url = flags.url || flags.token || flags._ && flags._[2];
5212
+ const action = flags.action || (sub === 'browser-start' ? 'open' : (sub === 'browser-snapshot' ? 'snapshot' : 'observe'));
5213
+ const packet = {
5214
+ type: sub === 'browser-start' ? 'browser_session' : (sub === 'browser-snapshot' ? 'browser_snapshot' : 'browser_step'),
5215
+ success: !flagEnabled(flags.failed),
5216
+ claim: flags.claim || flags.label || action,
5217
+ world: buildCommandWorld(null, { accountId, dataMode, branchName, workspace, host: normalizeBaseUrl(baseUrl) }),
5218
+ browser: {
5219
+ action,
5220
+ target: flags.target,
5221
+ value: flags.value,
5222
+ urlHash: url ? sha256(String(url)) : null,
5223
+ liveUrl: flagEnabled(flags['store-url']) ? url : undefined,
5224
+ interactionKind: flags.kind || (['click', 'fill', 'select', 'upload', 'reload'].includes(String(action)) ? 'user_interaction' : 'observation')
5225
+ },
5226
+ evidenceCategories: [sub, action === 'upload' ? 'browser_session.actual_upload' : null, action === 'click' ? 'browser_session.actual_clicks' : null]
5227
+ .filter(Boolean)
5228
+ };
5229
+ const response = await appendVerificationPacket(api, cwd, session, accountId, { 'verify-envelope': envelopeId }, packet);
5230
+ console.log('Browser evidence attached:', (response.packet || packet).packetId);
5231
+ return response;
5232
+ }
5233
+
5234
+ if (sub === 'test') {
5235
+ return await testCommand(Object.assign({}, flags, { 'verify-envelope': envelopeId }));
5236
+ }
5237
+ if (sub === 'token') {
5238
+ return await tokenCommand(Object.assign({}, flags, { 'verify-envelope': envelopeId }));
5239
+ }
5240
+ if (sub === 'stage') {
5241
+ return await pushComponentsCommand(Object.assign({}, flags, { 'verify-envelope': envelopeId }));
5242
+ }
5243
+ if (sub === 'sync') {
5244
+ return await syncComponentsCommand(Object.assign({}, flags, { 'verify-envelope': envelopeId }));
5245
+ }
5246
+ if (sub === 'tool') {
5247
+ return await toolCommand(Object.assign({}, flags, { 'verify-envelope': envelopeId }));
5248
+ }
5249
+
5250
+ if (sub === 'invalidate') {
5251
+ const reason = flags.reason || flags.message || flags._ && flags._.slice(2).join(' ') || 'manually invalidated';
5252
+ const response = await postVerificationCommand(api, cwd, session, accountId, 'invalidate', { envelopeId, reason });
5253
+ writeLocalVerificationEnvelope(cwd, response.envelope);
5254
+ console.log('Envelope marked stale:', envelopeId);
5255
+ return response.envelope;
5256
+ }
5257
+
5258
+ if (sub === 'status' || sub === 'report') {
5259
+ let statusPacket = null;
5260
+ try {
5261
+ const componentStatus = await loggedPost(api, cwd, '/cli/components', {
5262
+ token: session.token,
5263
+ accountId,
5264
+ branchName,
5265
+ workspace,
5266
+ dataMode,
5267
+ mode: 'status'
5268
+ }).then((r) => r.data);
5269
+ statusPacket = {
5270
+ type: 'component_status',
5271
+ success: componentStatus.success !== false,
5272
+ claim: 'Current component status for verification status check',
5273
+ world: buildCommandWorld(componentStatus, { accountId, dataMode, branchName, workspace, host: normalizeBaseUrl(baseUrl) }),
5274
+ revision: collectVerificationSource(cwd, flags),
5275
+ assertions: [],
5276
+ rawRefs: { command: 'components status' },
5277
+ componentStatus
5278
+ };
5279
+ await appendVerificationPacket(api, cwd, session, accountId, { 'verify-envelope': envelopeId }, statusPacket, { quiet: true });
5280
+ } catch (err) {
5281
+ if (!flagEnabled(flags.json)) {
5282
+ console.error('Warning: could not attach component_status packet before rendering report:', describeError(err));
5283
+ }
5284
+ }
5285
+ const response = await postVerificationCommand(api, cwd, session, accountId, sub, {
5286
+ envelopeId,
5287
+ currentWorld: buildCommandWorld(statusPacket && statusPacket.componentStatus, {
5288
+ accountId,
5289
+ dataMode,
5290
+ branchName,
5291
+ workspace,
5292
+ host: normalizeBaseUrl(baseUrl)
5293
+ })
5294
+ });
5295
+ if (response.envelope) writeLocalVerificationEnvelope(cwd, response.envelope);
5296
+ if (response.report) {
5297
+ console.log(response.report);
5298
+ } else if (response.evaluation || response.status) {
5299
+ console.log(JSON.stringify(response, null, 2));
5300
+ }
5301
+ return response;
5302
+ }
5303
+
5304
+ throw new Error('Unknown verify subcommand: ' + sub);
4735
5305
  }
4736
5306
 
4737
5307
  async function dataModeCommand(flags, subcommand) {
@@ -10343,6 +10913,35 @@ function printTokenHelp() {
10343
10913
  console.log('Inspect decodes a persisted Remits token and prints token metadata, recognized routing fields, safety/dataMode evidence, and full context.');
10344
10914
  }
10345
10915
 
10916
+ function printVerifyHelp() {
10917
+ console.log('Usage: remits-cli verify <subcommand>');
10918
+ console.log('');
10919
+ console.log('Envelope lifecycle:');
10920
+ console.log(' remits-cli verify start --summary "..." [--manifest file.json] [--ticket ID]');
10921
+ console.log(' remits-cli verify use <envelopeId>');
10922
+ console.log(' remits-cli verify current');
10923
+ console.log(' remits-cli verify clear');
10924
+ console.log(' remits-cli verify show [--envelope ID] [--json]');
10925
+ console.log(' remits-cli verify status [--envelope ID]');
10926
+ console.log(' remits-cli verify report [--envelope ID]');
10927
+ console.log('');
10928
+ console.log('Manifest and manual evidence:');
10929
+ console.log(' remits-cli verify manifest --file file.json [--envelope ID]');
10930
+ console.log(' remits-cli verify manifest --print [--envelope ID]');
10931
+ console.log(' remits-cli verify attach --artifact path --label "..." [--envelope ID]');
10932
+ console.log(' remits-cli verify attach --screenshot path --label "..." [--envelope ID]');
10933
+ console.log(' remits-cli verify attach --note "..." [--envelope ID]');
10934
+ console.log('');
10935
+ console.log('Wrappers around existing commands:');
10936
+ console.log(' remits-cli verify stage --workset');
10937
+ console.log(' remits-cli verify test --test "..." [--names "case"]');
10938
+ console.log(' remits-cli verify token --path /page');
10939
+ console.log(' remits-cli verify sync --safe');
10940
+ console.log(' remits-cli verify tool --name mcp_tool --input \'{...}\'');
10941
+ console.log('');
10942
+ console.log('Existing commands also accept --verify-envelope <id> and --no-verify-envelope.');
10943
+ }
10944
+
10346
10945
  async function main() {
10347
10946
  migrateSessionIfNeeded();
10348
10947
  const originalArgv = process.argv.slice(2);
@@ -10393,7 +10992,7 @@ async function main() {
10393
10992
  // sure the repo has the latest guides — authenticating first if needed — so
10394
10993
  // only platform-authenticated users ever receive them. `auth` syncs its own
10395
10994
  // guides, so it is excluded here.
10396
- const GUIDE_SYNC_COMMANDS = new Set(['components', 'test', 'token', 'tools', 'tool']);
10995
+ const GUIDE_SYNC_COMMANDS = new Set(['components', 'test', 'token', 'tools', 'tool', 'verify']);
10397
10996
  if (!wantsJson && !isHelpCmd && GUIDE_SYNC_COMMANDS.has(command)) {
10398
10997
  try { await ensureGuidesSynced(process.cwd(), args); } catch (_) { /* best-effort */ }
10399
10998
  }
@@ -10421,6 +11020,8 @@ async function main() {
10421
11020
  console.log(' remits-cli tool --name <toolName> [--base-url URL] [--branch BRANCH] [--input \"{...}\"|--input-file file.json] [--data-mode test|prod] [--scope self|children|hierarchy] [--target-account-id ID] [--account-ids 1,2,3] [--variant-branch NAME|none] [--timeout-ms 60000] [--async true --wait true]');
10422
11021
  console.log(' remits-cli tool status --call-id <callId> [--base-url URL] [--account-id ID] [--data-mode test|prod]');
10423
11022
  console.log(' remits-cli workspace [show|use <name>|use --auto|clear] # isolate staging when several agents share a repo');
11023
+ console.log(' remits-cli verify start --summary "..." [--manifest file.json] # start a verification envelope');
11024
+ console.log(' remits-cli verify [current|use|clear|status|report|attach|test|token|stage|sync|tool]');
10424
11025
  console.log(' remits-cli components stage [--workset|--changed-only] [--base-url URL] [--account-id ID] [--branch BRANCH] [--workspace NAME] [--data-mode test|prod] [--json|--verbose]');
10425
11026
  console.log(' remits-cli components status [--base-url URL] [--account-id ID] [--branch BRANCH] [--workspace NAME] [--component-type TYPE --component-id ID] [--json|--verbose]');
10426
11027
  console.log(' remits-cli components clear [--base-url URL] [--account-id ID] [--branch BRANCH] [--workspace NAME] [--component-type TYPE] [--component-id ID] [--all] [--json|--verbose]');
@@ -10502,6 +11103,11 @@ async function main() {
10502
11103
  process.exit(0);
10503
11104
  }
10504
11105
 
11106
+ if (wantsHelp && command === 'verify') {
11107
+ printVerifyHelp();
11108
+ process.exit(0);
11109
+ }
11110
+
10505
11111
  if (command === 'auth') {
10506
11112
  await authCommand(args);
10507
11113
  return;
@@ -10621,6 +11227,11 @@ async function main() {
10621
11227
  return;
10622
11228
  }
10623
11229
 
11230
+ if (command === 'verify') {
11231
+ await verifyCommand(args, subcommand);
11232
+ return;
11233
+ }
11234
+
10624
11235
  if (command === 'test' && subcommand === 'run') {
10625
11236
  await testCommand(args);
10626
11237
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remits/remits-cli",
3
- "version": "0.1.116",
3
+ "version": "0.1.117",
4
4
  "description": "Local CLI for auth, component sync, and live test execution against Remits",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -121,6 +121,11 @@ reference named after it.
121
121
  component (preferred, because it becomes regression protection) or a browser flow through
122
122
  `remits-cli token`. "Just do it" and "that's fine, commit it" are not evidence. If you genuinely
123
123
  cannot verify, say what you would need and ask. (`development-loop.md`)
124
+ - **For concrete user workflows, start a verification envelope before you edit.** `remits-cli verify
125
+ start --summary "..."` records the account/source/lane tuple and a manifest when you have one.
126
+ Stage, test, token, tool, and sync commands attach evidence automatically while the envelope is
127
+ active; finish from `remits-cli verify report`, which separates verified claims from missing or stale
128
+ evidence. (`development-loop.md`, `component-resolution.md`)
124
129
  - **Read the component's `.meta.yml` before changing behavior.** Sidecar descriptions can be dated
125
130
  decision records. Before changing a displayed value, helper, calculation, schema field, or prompt
126
131
  contract, check the sidecar and either preserve its decision or explicitly supersede it.
@@ -162,6 +167,8 @@ remits-cli tools # which tools this account actually has (tools
162
167
  ```
163
168
 
164
169
  plus the repo's `account-info.json` → `resolution` block for the account's shape.
170
+ For a workflow-shaped request, also run `remits-cli verify start --summary "..."` once the target tuple
171
+ is understood, then keep that envelope active through stage/test/token/sync.
165
172
 
166
173
  If any command returns 401, run `remits-cli auth` (with the same `--base-url` if you were targeting a
167
174
  non-default host).
@@ -15,6 +15,7 @@
15
15
  - [Hierarchy-scoped tool reads](#hierarchy-scoped-tool-reads)
16
16
  - [Data Mode](#data-mode)
17
17
  - [Command Reference](#command-reference)
18
+ - [Verification envelopes](#verification-envelopes)
18
19
  - [Staging modes: workset vs full snapshot](#staging-modes-workset-vs-full-snapshot)
19
20
  - [Prod banners and retryable failures](#prod-banners-and-retryable-failures)
20
21
 
@@ -209,8 +210,86 @@ remits-cli token inspect --token <token|tokenKey|URL> # inspect
209
210
  remits-cli tools [--branch <name>] [--data-mode test|prod] [--variant-branch <name|none>]
210
211
  remits-cli tool --name <toolName> [--branch <name>] [--input "{...}"] [--data-mode test|prod] [--variant-branch <name|none>] [--timeout-ms 60000] [--async true --wait true]
211
212
  remits-cli tool status --call-id <callId> [--data-mode test|prod]
213
+ remits-cli verify start --summary "..." [--manifest file.json] [--ticket ID]
214
+ remits-cli verify use <envelopeId>
215
+ remits-cli verify current
216
+ remits-cli verify clear
217
+ remits-cli verify show [--envelope ID] [--json]
218
+ remits-cli verify manifest --file file.json [--envelope ID]
219
+ remits-cli verify manifest --print [--envelope ID]
220
+ remits-cli verify attach --artifact path --label "..." [--envelope ID]
221
+ remits-cli verify attach --screenshot path --label "..." [--envelope ID]
222
+ remits-cli verify attach --note "..." [--envelope ID]
223
+ remits-cli verify browser-start --url URL [--label "..."]
224
+ remits-cli verify browser-step [--action click|fill|upload|reload|observe|...] [--target "..."] [--value "..."] [--url URL] [--label "..."]
225
+ remits-cli verify browser-snapshot [--url URL] [--label "..."]
226
+ remits-cli verify stage --workset
227
+ remits-cli verify test --test <id|name> [--names "case"]
228
+ remits-cli verify token --path <embeddablePathOrId>
229
+ remits-cli verify sync --safe
230
+ remits-cli verify tool --name <toolName> [--input "{...}"]
231
+ remits-cli verify status [--envelope ID]
232
+ remits-cli verify report [--envelope ID]
212
233
  ```
213
234
 
235
+ ### Verification envelopes
236
+
237
+ Use a verification envelope for workflow-shaped work: concrete user journeys, browser-facing changes,
238
+ branch variants, subscriber/forked accounts, production-vs-test lane questions, support tickets, and
239
+ multi-agent work. Start it after you know the target account/branch/workspace/data-mode tuple and before
240
+ the first edit:
241
+
242
+ ```bash
243
+ remits-cli workspace use --auto
244
+ remits-cli components status
245
+ remits-cli verify start --summary "Hosted upload updates an existing profile" --manifest acceptance.json
246
+ ```
247
+
248
+ An active envelope is stored in `./.remits-cli/verification/active`. Stage/test/token/tool/sync commands
249
+ attach evidence automatically while an envelope is active. The explicit wrappers do the same thing and
250
+ make the intent visible in terminal history:
251
+
252
+ ```bash
253
+ remits-cli verify stage --workset
254
+ remits-cli verify test --test "Adyen Import Recovery" --names "browser upload recovery"
255
+ remits-cli verify token --path /page/pricing-config --as-account 21 --data-mode test
256
+ remits-cli verify sync --safe
257
+ remits-cli verify report
258
+ ```
259
+
260
+ Existing commands also accept `--verify-envelope <id>` to attach to a specific envelope and
261
+ `--no-verify-envelope` to suppress automatic attachment for one command.
262
+
263
+ The manifest is the proof contract. It should name the world being exercised: repo account, host,
264
+ git/component branch, workspace, data mode, source layer, user journeys, artifacts and hashes, and the
265
+ evidence categories required before the final response may claim the work is done. If the manifest
266
+ cannot be written because the workflow is ambiguous, ask before implementation. For machine-gated report
267
+ requirements, prefer structured entries like `{"id":"actual_upload","packetType":"browser_step",
268
+ "category":"browser_session.actual_upload"}`. Plain strings work when they match a packet type or an
269
+ evidence category, but structured entries are clearer when several agents collect packets for one
270
+ envelope.
271
+
272
+ Common packet meanings:
273
+
274
+ | Packet | It proves | It does not prove by itself |
275
+ |---|---|---|
276
+ | `stage` | Which components were uploaded to the staging lane, including workset vs overlay | The requested behavior works |
277
+ | `component_status` | Which source world and lane are currently staged or clean | Any behavior was exercised |
278
+ | `test_run` | A Test component passed in the recorded account/branch/workspace/data lane | A hosted browser journey unless that Test actually drove it |
279
+ | `token_inspect` | A token was minted/inspected for a specific account, source layer, and lane | A user clicked through the page |
280
+ | `browser_session` / `browser_step` / `browser_snapshot` | The browser journey was opened or observed with named actions | Server-side persistence unless paired with assertions or follow-up state checks |
281
+ | `tool_call` | A platform tool ran in the recorded world and returned the recorded result | User-visible behavior unless the tool is the requested entry point |
282
+ | `sync_dry_run` | The planned durable writes and safety gates | Anything was written |
283
+ | `sync_mutation` | Durable source moved to trunk or variant and returned a sync SHA | The committed source behaves after staging is cleared |
284
+ | `artifact` | A file/screenshot/note exists with path, size, and sha256 | The workflow consumed it |
285
+
286
+ Final claims should come from `remits-cli verify report`. Treat `Verified` as the acceptance boundary.
287
+ `Additional evidence` is useful handoff context, but it does not satisfy a missing required packet unless
288
+ the report lists it under `Verified`. If the report says `partially_verified`, stale, or missing evidence,
289
+ say that plainly instead of widening the claim. In particular, staged proof is not committed variant/trunk
290
+ proof, a token is not browser proof, and a direct DOM or Alpine state mutation is not the same as a user
291
+ click/upload/reload flow.
292
+
214
293
  For tests specifically:
215
294
  - If `--data-mode` is omitted, `remits-cli test run` uses `test` and sends `dataModeSource:"cliDefault"`.
216
295
  An explicit `--data-mode prod` sends `dataModeSource:"explicitFlag"` so production test runs are
@@ -10,6 +10,7 @@
10
10
 
11
11
  - [Component Resolution: Staging Cache vs DB (which "version" actually runs)](#component-resolution-staging-cache-vs-db-which-version-actually-runs)
12
12
  - [The three source layers + the compile cache](#the-three-source-layers--the-compile-cache)
13
+ - [Verification envelopes record the source layer](#verification-envelopes-record-the-source-layer)
13
14
  - [Staging cache key format](#staging-cache-key-format)
14
15
  - [How the platform picks staged vs DB (the compile signature)](#how-the-platform-picks-staged-vs-db-the-compile-signature)
15
16
  - [When staged overrides apply](#when-staged-overrides-apply)
@@ -48,6 +49,18 @@ Plus the compile cache:
48
49
  cache of the parsed closure, keyed by `(componentId, type, compileSignature)`. This is why a change that
49
50
  is correctly in the DB can still execute stale on a running instance — see "Stale after sync" below.
50
51
 
52
+ ### Verification envelopes record the source layer
53
+
54
+ When a verification envelope is active, `components stage`, `components status`, `test run`, `token`,
55
+ `tool`, and `components sync` attach evidence packets with the branch/workspace/staging lane and the best
56
+ source-layer facts the command observed. A staged test packet is proof of the staged layer; a sync packet
57
+ is proof of a source transition; a token packet is proof of the browser token's resolution tuple. Those
58
+ are not interchangeable.
59
+
60
+ Use `remits-cli verify report` before summarizing the work. It will naturally say when the evidence only
61
+ covered staged source, when committed variant/trunk proof is missing, or when evidence became stale after
62
+ the git head, overlay, or platform sync moved.
63
+
51
64
  ### Staging cache key format
52
65
 
53
66
  ```
@@ -10,6 +10,7 @@
10
10
 
11
11
  - [Two Workflows](#two-workflows)
12
12
  - [Test Mode vs Prod Mode](#test-mode-vs-prod-mode)
13
+ - [Verification Envelopes](#verification-envelopes)
13
14
  - [Development Workflow](#development-workflow)
14
15
  - [The Golden Rule: Writing Code Is Not Finishing the Job](#the-golden-rule-writing-code-is-not-finishing-the-job)
15
16
  - [The Development Fast Loop](#the-development-fast-loop)
@@ -66,6 +67,36 @@ The right model is:
66
67
 
67
68
  Never treat "it looks right in prod data inspection" as sufficient proof that a code change is verified.
68
69
 
70
+ ## Verification Envelopes
71
+
72
+ For a concrete user workflow, browser-facing change, branch-variant release, support ticket, or any loop
73
+ where earlier manual testing found a gap, start a verification envelope before editing:
74
+
75
+ ```bash
76
+ remits-cli verify start --summary "Hosted upload updates an existing profile" --manifest acceptance.json
77
+ ```
78
+
79
+ The envelope records the account, host, data lane, branch, workspace, git heads, staging lane, and the
80
+ acceptance manifest. It is mirrored locally under `.remits-cli/verification/<envelopeId>/` and becomes
81
+ active for this checkout. While active, `components stage`, `components status`, `test run`, `token`,
82
+ `token inspect`, `tool`, and `components sync` attach evidence packets automatically; pass
83
+ `--verify-envelope <id>` to name one explicitly or `--no-verify-envelope` when a command should not be
84
+ attached.
85
+
86
+ Use the wrappers when you want the intent to be unmistakable:
87
+
88
+ ```bash
89
+ remits-cli verify stage --workset
90
+ remits-cli verify test --test "Suite" --names "case name"
91
+ remits-cli verify token --path /page/example
92
+ remits-cli verify sync --safe
93
+ remits-cli verify attach --artifact sample.zip --label "user supplied ZIP"
94
+ remits-cli verify report
95
+ ```
96
+
97
+ The report is the final-response source. It separates verified claims, missing evidence, stale packets,
98
+ and the source/account/lane tuple, so do not replace it with a generic "verified" sentence.
99
+
69
100
  ## Development Workflow
70
101
 
71
102
  ### The Golden Rule: Writing Code Is Not Finishing the Job
@@ -124,6 +155,10 @@ runs resolve and what a sync writes. If `account-info.json` carries a `component
124
155
  variants of these components exist: editing an origin component will drift them, so check
125
156
  `remits-cli components branches` before changing shared code. See `branch-variants.md`.
126
157
 
158
+ If the request names a journey or acceptance behavior, start the envelope here, after the target tuple is
159
+ understood and before editing. A manifest can be lightweight JSON; the point is that the required
160
+ evidence is durable before the proof is collected.
161
+
127
162
  #### Step 2: Make the Change
128
163
  Edit component files under `components/`. This is local file editing — the platform doesn't know about your changes yet.
129
164
 
@@ -17,6 +17,7 @@
17
17
  - [Before you edit anything: where you are, and whether you may](#before-you-edit-anything-where-you-are-and-whether-you-may)
18
18
  - [Your account's process is binding, and it is already in your brief](#your-accounts-process-is-binding-and-it-is-already-in-your-brief)
19
19
  - [Some of that process is enforced, not requested](#some-of-that-process-is-enforced-not-requested)
20
+ - [Verification envelopes on tickets](#verification-envelopes-on-tickets)
20
21
  - [Seeing the queue as a human does](#seeing-the-queue-as-a-human-does)
21
22
  - [Agent components are workers too](#agent-components-are-workers-too)
22
23
  - [Moving a ticket through its lifecycle](#moving-a-ticket-through-its-lifecycle)
@@ -191,6 +192,26 @@ start.
191
192
  `--data-mode test` registers a fixture agent instead, which will never be routed a production ticket —
192
193
  use it only when you are deliberately testing the routing itself.
193
194
 
195
+ ### Verification envelopes on tickets
196
+
197
+ For ticket work that changes component behavior, especially browser workflows, branch variants, release
198
+ gates, or a previously failed manual loop, start an envelope tied to the ticket:
199
+
200
+ ```bash
201
+ remits-cli verify start --ticket 22454 --summary "Fix hosted upload recovery"
202
+ ```
203
+
204
+ Keep it active while you stage, run Tests, mint browser tokens, call tools, and sync. Those commands
205
+ attach evidence packets to the platform envelope and mirror them locally under `.remits-cli/verification/`.
206
+ Before completing or handing off the ticket, run:
207
+
208
+ ```bash
209
+ remits-cli verify report
210
+ ```
211
+
212
+ Use that output in the ticket update. It names what was verified, what is still missing, and whether the
213
+ proof covered staged source or committed source.
214
+
194
215
  ### If you are the worker
195
216
 
196
217
  You know you are one when `REMITS_SUPPORT_TICKET_ID` is set in your environment. Your whole job is