@remits/remits-cli 0.1.128 → 0.1.129
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
|
@@ -6,16 +6,16 @@
|
|
|
6
6
|
- L22 Runtime Bootstrap And Shared State
|
|
7
7
|
- L116 Sessions, Account Resolution, And Production Guards
|
|
8
8
|
- L595 Local State, Workspaces, And Verification Evidence
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
9
|
+
- L1448 Account Repos, Guide Sync, And Platform Repo
|
|
10
|
+
- L2106 Component Discovery And HTTP Logging
|
|
11
|
+
- L2598 Skill Delivery And TOC Resolution
|
|
12
|
+
- L2908 Auth And Component Staging
|
|
13
|
+
- L3506 Component Summaries, Status, And Sync Gates
|
|
14
|
+
- L5457 Branches, Promotion, Commit, And Test Runs
|
|
15
|
+
- L6682 Tokens, Tools, Verification, And Config
|
|
16
|
+
- L7598 Service Dashboard And WebSocket Listener
|
|
17
|
+
- L9693 Agent And Ticket Workflows
|
|
18
|
+
- L12751 Help, Auto Update, And Command Dispatch
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
/*
|
|
@@ -1107,6 +1107,43 @@ async function postVerificationCommand(api, cwd, session, accountId, command, pa
|
|
|
1107
1107
|
return response;
|
|
1108
1108
|
}
|
|
1109
1109
|
|
|
1110
|
+
// The repo/session account a packet was recorded from, stated explicitly in its world. `world.accountId` is the
|
|
1111
|
+
// account the command EXECUTED as, so an --as-account run carried the subscriber there and a manifest's
|
|
1112
|
+
// repoAccountId could never match it. Only fills a missing value; a packet that already knows keeps its own.
|
|
1113
|
+
function stampPacketRepoAccount(packet, accountId) {
|
|
1114
|
+
if (!packet || accountId === undefined || accountId === null || accountId === '') return packet;
|
|
1115
|
+
const repoAccountId = Number(accountId);
|
|
1116
|
+
if (!Number.isFinite(repoAccountId)) return packet;
|
|
1117
|
+
if (!packet.world || typeof packet.world !== 'object') packet.world = {};
|
|
1118
|
+
if (packet.world.repoAccountId === undefined || packet.world.repoAccountId === null) {
|
|
1119
|
+
packet.world.repoAccountId = repoAccountId;
|
|
1120
|
+
}
|
|
1121
|
+
return packet;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
// A sync packet is evidence of WHAT a sync planned or wrote, not a second copy of the preview. The per-field
|
|
1125
|
+
// trunk/branch text a dry run attaches for the admin diff panel is up to 400 KB per sync, and storing it on
|
|
1126
|
+
// every packet made one busy envelope 9.4 MB — slow enough that `verify show` timed out. Field NAMES
|
|
1127
|
+
// (`changedFields`) stay; the full plan is always one `components sync --dry-run` away.
|
|
1128
|
+
function syncEvidencePayload(sync) {
|
|
1129
|
+
if (!sync || typeof sync !== 'object') return sync;
|
|
1130
|
+
const results = sync.syncResults;
|
|
1131
|
+
if (!results || typeof results !== 'object') return sync;
|
|
1132
|
+
const trimmedResults = {};
|
|
1133
|
+
Object.keys(results).forEach((bucket) => {
|
|
1134
|
+
const entries = results[bucket];
|
|
1135
|
+
trimmedResults[bucket] = Array.isArray(entries)
|
|
1136
|
+
? entries.map((entry) => {
|
|
1137
|
+
if (!entry || typeof entry !== 'object' || !('fields' in entry)) return entry;
|
|
1138
|
+
const copy = Object.assign({}, entry);
|
|
1139
|
+
delete copy.fields;
|
|
1140
|
+
return copy;
|
|
1141
|
+
})
|
|
1142
|
+
: entries;
|
|
1143
|
+
});
|
|
1144
|
+
return Object.assign({}, sync, { syncResults: trimmedResults });
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1110
1147
|
async function appendVerificationPacket(api, cwd, session, accountId, flags, packet, options = {}) {
|
|
1111
1148
|
const context = activeVerificationContext(cwd, flags, session, accountId);
|
|
1112
1149
|
const envelopeId = verificationEnvelopeIdForCommand(cwd, flags, context);
|
|
@@ -1117,6 +1154,7 @@ async function appendVerificationPacket(api, cwd, session, accountId, flags, pac
|
|
|
1117
1154
|
success: true
|
|
1118
1155
|
}, packet || {});
|
|
1119
1156
|
finalPacket.envelopeId = envelopeId;
|
|
1157
|
+
stampPacketRepoAccount(finalPacket, accountId);
|
|
1120
1158
|
writeLocalVerificationPacket(cwd, envelopeId, finalPacket);
|
|
1121
1159
|
try {
|
|
1122
1160
|
const response = await postVerificationCommand(api, cwd, session, accountId, 'packet', {
|
|
@@ -1281,6 +1319,17 @@ function buildCommandWorld(response, fallback = {}) {
|
|
|
1281
1319
|
};
|
|
1282
1320
|
}
|
|
1283
1321
|
|
|
1322
|
+
// A variant sync writes overlays on exactly one component branch; say so in the packet world, where a manifest's
|
|
1323
|
+
// componentBranch is compared. Without it no sync_mutation could satisfy a manifest that declares one.
|
|
1324
|
+
function syncPacketWorld(response, world, branchName) {
|
|
1325
|
+
const sync = (response && response.sync) || {};
|
|
1326
|
+
const out = Object.assign({}, world || {});
|
|
1327
|
+
if (sync.mode === 'variant' && !out.componentBranch) {
|
|
1328
|
+
out.componentBranch = sync.branchName || branchName || null;
|
|
1329
|
+
}
|
|
1330
|
+
return out;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1284
1333
|
function laneContentHashFrom(response) {
|
|
1285
1334
|
if (!response) return null;
|
|
1286
1335
|
const laneSummary = response.laneSummary || (response.staging && response.staging.laneSummary) ||
|
|
@@ -4542,7 +4591,7 @@ async function syncComponentsCommand(rawFlags) {
|
|
|
4542
4591
|
type: response.sync && response.sync.dryRun ? 'sync_dry_run' : 'sync_mutation',
|
|
4543
4592
|
success: response.success !== false,
|
|
4544
4593
|
claim: response.sync && response.sync.dryRun ? 'Component sync dry-run observed' : 'Component sync completed',
|
|
4545
|
-
world: buildCommandWorld(response, { accountId, dataMode, branchName, workspace, host: normalizeBaseUrl(baseUrl), sourceLayer: response.sync && response.sync.mode === 'variant' ? 'variant' : 'trunk' }),
|
|
4594
|
+
world: syncPacketWorld(response, buildCommandWorld(response, { accountId, dataMode, branchName, workspace, host: normalizeBaseUrl(baseUrl), sourceLayer: response.sync && response.sync.mode === 'variant' ? 'variant' : 'trunk' }), branchName),
|
|
4546
4595
|
revision: Object.assign(collectVerificationSource(cwd, flags), {
|
|
4547
4596
|
platformSyncedSha: response.sync && (response.sync.postSyncSha || response.sync.branchHeadSha)
|
|
4548
4597
|
}),
|
|
@@ -4551,7 +4600,7 @@ async function syncComponentsCommand(rawFlags) {
|
|
|
4551
4600
|
{ category: response.sync && response.sync.dryRun ? 'sync_dry_run' : 'sync_mutation', summary }
|
|
4552
4601
|
],
|
|
4553
4602
|
rawRefs: { command: 'components sync' },
|
|
4554
|
-
sync: response.sync,
|
|
4603
|
+
sync: syncEvidencePayload(response.sync),
|
|
4555
4604
|
summary
|
|
4556
4605
|
}, { quiet: flagEnabled(flags.json) });
|
|
4557
4606
|
|
|
@@ -4582,11 +4631,31 @@ async function syncComponentsCommand(rawFlags) {
|
|
|
4582
4631
|
return response;
|
|
4583
4632
|
}
|
|
4584
4633
|
console.log('Components sync:', JSON.stringify(response, null, 2));
|
|
4634
|
+
printVariantSyncWrites(summary);
|
|
4585
4635
|
printPromotionSignals(response);
|
|
4586
4636
|
failOnSyncGate(gate);
|
|
4587
4637
|
return response;
|
|
4588
4638
|
}
|
|
4589
4639
|
|
|
4640
|
+
// The one line a person needs from a variant sync of a long-lived branch: what THIS sync changes, apart from
|
|
4641
|
+
// the overlays it merely re-confirms (the totals alone read the same on every sync).
|
|
4642
|
+
function printVariantSyncWrites(summary) {
|
|
4643
|
+
const writes = summary && summary.writes;
|
|
4644
|
+
if (!writes || summary.mode !== 'variant') return;
|
|
4645
|
+
console.log('');
|
|
4646
|
+
if (!writes.known) {
|
|
4647
|
+
console.log('Overlay changes: unknown (this platform does not report which overlays were already stored).');
|
|
4648
|
+
return;
|
|
4649
|
+
}
|
|
4650
|
+
const verb = summary.dryRun ? 'would change' : 'changed';
|
|
4651
|
+
console.log('Overlay changes: ' + writes.changed + ' ' + verb + ', ' + writes.alreadyCurrent + ' already current, ' +
|
|
4652
|
+
writes.removed + ' removed, ' + writes.pruned + ' pruned (converged back to trunk)');
|
|
4653
|
+
(writes.changedComponents || []).forEach((entry) => {
|
|
4654
|
+
console.log(' ' + String(entry.type || 'component').toLowerCase() + ':' + (entry.id != null ? entry.id : entry.name) +
|
|
4655
|
+
(entry.name && entry.id != null ? ' ' + entry.name : ''));
|
|
4656
|
+
});
|
|
4657
|
+
}
|
|
4658
|
+
|
|
4590
4659
|
// A sync is a step in a longer loop, and the steps after it are the ones that get skipped — so print what
|
|
4591
4660
|
// the server just observed about this branch's relationship to trunk, not only what it wrote.
|
|
4592
4661
|
function printPromotionSignals(response) {
|
|
@@ -4755,7 +4824,9 @@ function syncPlanEntries(response) {
|
|
|
4755
4824
|
type: String(entry.type || entry.kind || 'component').toLowerCase(),
|
|
4756
4825
|
id: entry.id == null ? null : String(entry.id),
|
|
4757
4826
|
name: entry.name || null,
|
|
4758
|
-
path: entry.path || null
|
|
4827
|
+
path: entry.path || null,
|
|
4828
|
+
// true when the overlay already stored for this component matches the branch: re-confirmed, not changed.
|
|
4829
|
+
storedCurrent: entry.storedCurrent === true
|
|
4759
4830
|
});
|
|
4760
4831
|
});
|
|
4761
4832
|
});
|
|
@@ -4801,6 +4872,8 @@ function evaluateSyncGates(response, flags, changedFromWorkingTree, cwd) {
|
|
|
4801
4872
|
const expectedRemoved = parseExpectedRemoved(flags);
|
|
4802
4873
|
const violations = [];
|
|
4803
4874
|
const checks = {};
|
|
4875
|
+
// Overlays the --changed-only gate did not count because the platform already stores them with this content.
|
|
4876
|
+
let alreadyCurrentCount = null;
|
|
4804
4877
|
|
|
4805
4878
|
if (flagEnabled(flags['fail-on-errors']) || flagEnabled(flags.failOnErrors)) {
|
|
4806
4879
|
checks.failOnErrors = errors.length === 0;
|
|
@@ -4840,13 +4913,20 @@ function evaluateSyncGates(response, flags, changedFromWorkingTree, cwd) {
|
|
|
4840
4913
|
const allowedIds = new Set(changedSet.filter((c) => c.id != null).map((c) => c.type + ':' + c.id));
|
|
4841
4914
|
const allowedNames = new Set(changedSet.filter((c) => c.name).map((c) => c.type + ':' + String(c.name).toLowerCase()));
|
|
4842
4915
|
const allowedPaths = new Set([].concat(...changedSet.map((c) => (Array.isArray(c.paths) ? c.paths : []))).map(String));
|
|
4916
|
+
// An overlay the platform ALREADY stores with this exact content is not something this sync changes. A
|
|
4917
|
+
// long-lived branch (a permanent customer release train) re-reports every one of its overlays on every sync,
|
|
4918
|
+
// so counting those made the gate refuse ordinary work with "plan touches 80 components" and blame a stale
|
|
4919
|
+
// branch that `components promotion` reported as ready. Removals are never exempt.
|
|
4920
|
+
const alreadyCurrent = entries.filter((entry) => entry.storedCurrent && entry.bucket !== 'removed');
|
|
4843
4921
|
const unexpected = entries.filter((entry) => {
|
|
4922
|
+
if (entry.storedCurrent && entry.bucket !== 'removed') return false;
|
|
4844
4923
|
if (entry.path && allowedPaths.has(String(entry.path))) return false;
|
|
4845
4924
|
if (entry.id != null && allowedIds.has(entry.type + ':' + entry.id)) return false;
|
|
4846
4925
|
if (entry.name && allowedNames.has(entry.type + ':' + String(entry.name).toLowerCase())) return false;
|
|
4847
4926
|
return true;
|
|
4848
4927
|
});
|
|
4849
4928
|
checks.changedOnly = unexpected.length === 0;
|
|
4929
|
+
alreadyCurrentCount = alreadyCurrent.length;
|
|
4850
4930
|
if (unexpected.length) {
|
|
4851
4931
|
// Name the most likely cause instead of only the symptom. An empty changed set with a non-empty
|
|
4852
4932
|
// plan is the ordinary post-commit state, not evidence that the plan is dangerous.
|
|
@@ -4858,11 +4938,10 @@ function evaluateSyncGates(response, flags, changedFromWorkingTree, cwd) {
|
|
|
4858
4938
|
// physically carries old copies of files nobody on it touched, and a variant sync turns each of
|
|
4859
4939
|
// those into an unrelated override. Naming the cause is the difference between a gate that
|
|
4860
4940
|
// stops the damage and a gate that also tells you how to clear it.
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
'\n git merge <trunk> && git push origin <branch>'
|
|
4941
|
+
// With --changed-since the empty-set hint above does not apply, so a ref that shows no committed change
|
|
4942
|
+
// still needs the cause named — that is exactly the behind-trunk branch whose plan is all stale copies.
|
|
4943
|
+
: ((changedSet.length || since) && unexpected.length > changedSet.length)
|
|
4944
|
+
? staleBranchGateHint(response)
|
|
4866
4945
|
: '';
|
|
4867
4946
|
violations.push('--changed-only: plan touches ' + unexpected.length + ' component(s) this checkout did not change: ' +
|
|
4868
4947
|
unexpected.slice(0, 20).map(syncEntryToken).join(', ') + (unexpected.length > 20 ? ', ...' : '') + hint);
|
|
@@ -4870,7 +4949,28 @@ function evaluateSyncGates(response, flags, changedFromWorkingTree, cwd) {
|
|
|
4870
4949
|
}
|
|
4871
4950
|
}
|
|
4872
4951
|
|
|
4873
|
-
return { violations, checks };
|
|
4952
|
+
return { violations, checks, alreadyCurrentCount };
|
|
4953
|
+
}
|
|
4954
|
+
|
|
4955
|
+
// Name the stale-branch cause only when the platform's own git comparison says the branch IS behind trunk. The
|
|
4956
|
+
// hint used to fire on the count alone, and told agents on an up-to-date permanent branch to merge trunk in.
|
|
4957
|
+
function staleBranchGateHint(response) {
|
|
4958
|
+
const sync = (response && response.sync) || {};
|
|
4959
|
+
const comparison = sync.branchComparison || (sync.promotion && sync.promotion.git) || null;
|
|
4960
|
+
const behindBy = comparison && Number.isFinite(Number(comparison.behindBy)) ? Number(comparison.behindBy) : null;
|
|
4961
|
+
if (behindBy !== null && behindBy > 0) {
|
|
4962
|
+
return '\n Most likely cause: this branch is BEHIND trunk by ' + behindBy + ' commit(s) and still carries old copies ' +
|
|
4963
|
+
'of files it never changed. A variant sync turns each of those into an unrelated override.' +
|
|
4964
|
+
'\n Next step: merge trunk into this branch, push, then re-run the same command:' +
|
|
4965
|
+
'\n git merge <trunk> && git push origin <branch>';
|
|
4966
|
+
}
|
|
4967
|
+
if (behindBy === 0) {
|
|
4968
|
+
return '\n The branch is current with trunk, so these are not stale copies: they are overlays whose stored content ' +
|
|
4969
|
+
'differs from this branch but that this checkout\'s changed set does not name. Widen --changed-since to the ' +
|
|
4970
|
+
'commit the platform last synced (components promotion shows it), or review the plan with --dry-run.';
|
|
4971
|
+
}
|
|
4972
|
+
return '\n The platform could not compare this branch with trunk. Run `remits-cli components promotion` to see ' +
|
|
4973
|
+
'whether it is behind before merging anything.';
|
|
4874
4974
|
}
|
|
4875
4975
|
|
|
4876
4976
|
function failOnSyncGate(gate) {
|
|
@@ -5163,6 +5263,36 @@ function printTrunkSyncWarning(trunkBranch, options) {
|
|
|
5163
5263
|
emit(' For a gated plan, work on a variant branch instead.');
|
|
5164
5264
|
}
|
|
5165
5265
|
|
|
5266
|
+
function variantSyncWrites(overridden, added, removed, unchanged) {
|
|
5267
|
+
const overlays = overridden.concat(added);
|
|
5268
|
+
const known = overlays.every((entry) => entry && typeof entry.storedCurrent === 'boolean');
|
|
5269
|
+
const pruned = unchanged.filter((entry) => entry && entry.pruned === true).length;
|
|
5270
|
+
if (!known) {
|
|
5271
|
+
return { changed: null, alreadyCurrent: null, removed: removed.length, pruned, known: false };
|
|
5272
|
+
}
|
|
5273
|
+
const changed = overlays.filter((entry) => !entry.storedCurrent);
|
|
5274
|
+
return {
|
|
5275
|
+
changed: changed.length,
|
|
5276
|
+
changedComponents: changed.slice(0, 50).map((entry) => ({ type: entry.type, name: entry.name, id: entry.id })),
|
|
5277
|
+
alreadyCurrent: overlays.length - changed.length,
|
|
5278
|
+
removed: removed.length,
|
|
5279
|
+
pruned,
|
|
5280
|
+
known: true
|
|
5281
|
+
};
|
|
5282
|
+
}
|
|
5283
|
+
|
|
5284
|
+
// `|` separates selectors and a repeated --names needs no separator. A COMMA is never a separator any more: it
|
|
5285
|
+
// is sent as part of the selector, and the platform matches a comma-bearing selector either as one whole case name
|
|
5286
|
+
// ("… the complete statement, offer, and surcharge matrix") or, for older invocations, as its comma-separated
|
|
5287
|
+
// parts (Test.selectorMatchesCase). Splitting in the CLI took the real name apart before the platform could see it.
|
|
5288
|
+
function parseTestCaseSelectors(rawNames) {
|
|
5289
|
+
return (Array.isArray(rawNames) ? rawNames : [rawNames])
|
|
5290
|
+
.filter((value) => value !== undefined && value !== null)
|
|
5291
|
+
.flatMap((value) => String(value).split('|'))
|
|
5292
|
+
.map((value) => value.trim())
|
|
5293
|
+
.filter(Boolean);
|
|
5294
|
+
}
|
|
5295
|
+
|
|
5166
5296
|
function buildSyncSummary(response) {
|
|
5167
5297
|
const sync = (response && response.sync) || {};
|
|
5168
5298
|
const results = sync.syncResults || {};
|
|
@@ -5292,6 +5422,10 @@ function buildSyncSummary(response) {
|
|
|
5292
5422
|
gateViolations: (response && response.gateViolations) || null,
|
|
5293
5423
|
overridden: overridden.length,
|
|
5294
5424
|
added: added.length,
|
|
5425
|
+
// What THIS sync changes, as opposed to the whole overlay set it re-reports. `overridden`/`added` are totals,
|
|
5426
|
+
// so five consecutive syncs of a long-lived branch all printed `overridden: 48, added: 32` and nobody could see
|
|
5427
|
+
// which overlays their own sync moved. Null counts mean the platform predates the storedCurrent flag.
|
|
5428
|
+
writes: variantSyncWrites(overridden, added, removed, unchanged),
|
|
5295
5429
|
removed: removed.map((entry) => ({
|
|
5296
5430
|
type: entry.type,
|
|
5297
5431
|
kind: entry.kind,
|
|
@@ -6345,26 +6479,12 @@ async function testCommand(flags) {
|
|
|
6345
6479
|
throw new Error('Missing --test <id-or-name>');
|
|
6346
6480
|
}
|
|
6347
6481
|
|
|
6348
|
-
// Test case names are English prose, so COMMAS occur in them naturally.
|
|
6349
|
-
//
|
|
6350
|
-
// report unmatched selectors) reported a clean run of zero cases. `|` is the delimiter now; the
|
|
6351
|
-
// comma still works when no `|` is present, so existing invocations keep behaving as before.
|
|
6352
|
-
// `--names` may also be repeated, which needs no delimiter at all.
|
|
6482
|
+
// Test case names are English prose, so COMMAS occur in them naturally. `|` (or repeating --names) is the
|
|
6483
|
+
// only delimiter; see parseTestCaseSelectors for how a comma-bearing selector is matched.
|
|
6353
6484
|
const rawNames = flags.names === undefined || flags.names === null
|
|
6354
6485
|
? []
|
|
6355
6486
|
: (Array.isArray(flags.names) ? flags.names : [flags.names]);
|
|
6356
|
-
const
|
|
6357
|
-
const names = rawNames
|
|
6358
|
-
.flatMap((value) => {
|
|
6359
|
-
const text = String(value);
|
|
6360
|
-
if (text.includes('|')) return text.split('|');
|
|
6361
|
-
// Comma-splitting is the legacy behaviour, kept so existing invocations still work. It is applied
|
|
6362
|
-
// ONLY to a single --names value: repeating the flag is already unambiguous, so splitting there
|
|
6363
|
-
// would take a deliberate literal name back apart.
|
|
6364
|
-
return repeated ? [text] : text.split(',');
|
|
6365
|
-
})
|
|
6366
|
-
.map((s) => s.trim())
|
|
6367
|
-
.filter(Boolean);
|
|
6487
|
+
const names = parseTestCaseSelectors(rawNames);
|
|
6368
6488
|
|
|
6369
6489
|
const api = buildAxios(baseUrl, session.token);
|
|
6370
6490
|
// --as-account runs the Test AS a (descendant) subscriber account, so that account's relationship
|
package/package.json
CHANGED
|
@@ -363,7 +363,9 @@ The analogous hazard is different, and you must still respect it:
|
|
|
363
363
|
explicitly.
|
|
364
364
|
- **Use `components sync --dry-run` before risky variant syncs.** It reports `overridden`, `added`,
|
|
365
365
|
`removed`, `unchanged`, `skipped`, and `errors` without writing rows, caching the sync SHA, or clearing
|
|
366
|
-
staging.
|
|
366
|
+
staging. `overridden`/`added` are the branch's WHOLE overlay set; read the `Overlay changes:` line (or
|
|
367
|
+
`writes` in `--summary`) for what this sync actually changes, and `storedCurrent: true` on an entry for
|
|
368
|
+
an overlay that is already stored as-is. Existing overlay ids appear as `variantId`; an `unchanged` row with `pruned:true` means the
|
|
367
369
|
branch has converged back to trunk and the overlay would be removed. It is rejected on trunk, and
|
|
368
370
|
`components commit --dry-run` is unsupported because `commit` performs compile validation and local git
|
|
369
371
|
writes before syncing.
|
|
@@ -341,8 +341,9 @@ For tests specifically:
|
|
|
341
341
|
- If `--data-mode` is omitted, `remits-cli test run` uses `test` and sends `dataModeSource:"cliDefault"`.
|
|
342
342
|
An explicit `--data-mode prod` sends `dataModeSource:"explicitFlag"` so production test runs are
|
|
343
343
|
auditable from the server status payload even when the original terminal history is gone.
|
|
344
|
-
- `--names` is `|`-delimited
|
|
345
|
-
selector fails the run instead of
|
|
344
|
+
- `--names` is `|`-delimited and may be repeated. A selector containing commas matches a case with that
|
|
345
|
+
exact name first, else its comma-separated parts; an unmatched selector fails the run instead of
|
|
346
|
+
reporting zero cases as success.
|
|
346
347
|
- `--as-account <ID>` runs AS a descendant subscriber so its edge selects the component branch
|
|
347
348
|
(*"what does customer X get?"*); `--variant-branch <name>` probes a branch from any checkout
|
|
348
349
|
(*"what does branch Y look like?"*), and `--variant-branch none` forces production/subscription semantics.
|