@remits/remits-cli 0.1.130 → 0.1.133

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
  - L119 Sessions, Account Resolution, And Production Guards
8
8
  - L603 Local State, Workspaces, And Verification Evidence
9
- - L2018 Account Repos, Guide Sync, And Platform Repo
10
- - L2676 Component Discovery And HTTP Logging
11
- - L3252 Skill Delivery And TOC Resolution
12
- - L3562 Auth And Component Staging
13
- - L4171 Component Summaries, Status, And Sync Gates
14
- - L6204 Branches, Promotion, Commit, And Test Runs
15
- - L7813 Tokens, Tools, Verification, And Config
16
- - L8943 Service Dashboard And WebSocket Listener
17
- - L11059 Agent And Ticket Workflows
18
- - L14117 Help, Auto Update, And Command Dispatch
9
+ - L2255 Account Repos, Guide Sync, And Platform Repo
10
+ - L2949 Component Discovery And HTTP Logging
11
+ - L3525 Skill Delivery And TOC Resolution
12
+ - L3835 Auth And Component Staging
13
+ - L4449 Component Summaries, Status, And Sync Gates
14
+ - L6518 Branches, Promotion, Commit, And Test Runs
15
+ - L8183 Tokens, Tools, Verification, And Config
16
+ - L9405 Service Dashboard And WebSocket Listener
17
+ - L11521 Agent And Ticket Workflows
18
+ - L14815 Help, Auto Update, And Command Dispatch
19
19
  */
20
20
 
21
21
  /*
@@ -1411,13 +1411,36 @@ function verificationPaths(cwd, envelopeId) {
1411
1411
  };
1412
1412
  }
1413
1413
 
1414
+ // The active envelope is bound to the checkout world an agent WORKS in: host, account, branch, workspace - the
1415
+ // same identity as its staging lane. The data lane is deliberately NOT part of it. Commands resolve their lane
1416
+ // differently (`test run` defaults to test; stage/token/tool/sync follow the session), so a lane in this key made
1417
+ // one agent see a different envelope - or none - from one command to the next: evidence silently never attached,
1418
+ // and `verify report` answered "No verification envelope selected" for the envelope it had just started. Whether
1419
+ // a command's lane fits the envelope is the platform's call (`verificationPreflight`), and it is made out loud.
1414
1420
  function verificationContextKey(context = {}) {
1415
1421
  const baseUrl = normalizeBaseUrl(context.baseUrl || context.host || DEFAULT_BASE_URL);
1416
1422
  const accountId = context.accountId != null ? String(context.accountId) : 'unknown-account';
1417
1423
  const branchName = context.branchName || context.branch || 'unknown-branch';
1418
1424
  const workspace = normalizeWorkspace(context.workspace) || 'shared';
1419
- const dataMode = normalizeDataMode(context.dataMode);
1420
- return [baseUrl, accountId, branchName, workspace, dataMode].join('|');
1425
+ return [baseUrl, accountId, branchName, workspace].join('|');
1426
+ }
1427
+
1428
+ // Pointers written while the data lane was still part of the key end in `|test` / `|prod`. They name the same
1429
+ // checkout world, so read them (newest first) rather than orphan an envelope an older CLI selected.
1430
+ function legacyVerificationContextKeys(key) {
1431
+ return [key + '|test', key + '|prod'];
1432
+ }
1433
+
1434
+ function activeVerificationContextEntry(contexts, key) {
1435
+ const normalize = (entry) => (typeof entry === 'string' ? { envelopeId: entry } : entry);
1436
+ const current = contexts && contexts[key];
1437
+ if (current) return normalize(current);
1438
+ const legacy = legacyVerificationContextKeys(key)
1439
+ .map((legacyKey) => contexts && contexts[legacyKey])
1440
+ .filter((entry) => entry && (typeof entry === 'string' || entry.envelopeId))
1441
+ .map(normalize)
1442
+ .sort((left, right) => String(right.updatedAt || '').localeCompare(String(left.updatedAt || '')));
1443
+ return legacy[0] || null;
1421
1444
  }
1422
1445
 
1423
1446
  function readActiveVerificationContexts(cwd) {
@@ -1452,11 +1475,8 @@ function activeVerificationContext(cwd, flags = {}, session = null, accountId =
1452
1475
 
1453
1476
  function readActiveVerificationEnvelope(cwd, context = null) {
1454
1477
  if (context) {
1455
- const contexts = readActiveVerificationContexts(cwd);
1456
- const entry = contexts[verificationContextKey(context)];
1457
- if (entry && typeof entry === 'object') return entry.envelopeId || null;
1458
- if (typeof entry === 'string') return entry || null;
1459
- return null;
1478
+ const entry = activeVerificationContextEntry(readActiveVerificationContexts(cwd), verificationContextKey(context));
1479
+ return entry && entry.envelopeId ? String(entry.envelopeId) : null;
1460
1480
  }
1461
1481
  const paths = verificationPaths(cwd);
1462
1482
  const candidates = [paths.activeFile, localStatePaths(cwd).legacy.activeVerificationFile];
@@ -1477,6 +1497,7 @@ function writeActiveVerificationEnvelope(cwd, envelopeId, context = null) {
1477
1497
  if (context) {
1478
1498
  const contexts = readActiveVerificationContexts(cwd);
1479
1499
  const key = verificationContextKey(context);
1500
+ legacyVerificationContextKeys(key).forEach((legacyKey) => { delete contexts[legacyKey]; });
1480
1501
  if (!envelopeId) {
1481
1502
  delete contexts[key];
1482
1503
  } else {
@@ -1499,10 +1520,15 @@ function writeActiveVerificationEnvelope(cwd, envelopeId, context = null) {
1499
1520
  return String(envelopeId);
1500
1521
  }
1501
1522
 
1523
+ function explicitVerificationEnvelopeId(flags = {}) {
1524
+ const explicit = flags['verify-envelope'] || flags.verifyEnvelope || flags.envelope;
1525
+ return explicit !== undefined && explicit !== null && explicit !== true ? (String(explicit).trim() || null) : null;
1526
+ }
1527
+
1502
1528
  function verificationEnvelopeIdForCommand(cwd, flags = {}, context = null) {
1503
1529
  if (flagEnabled(flags['no-verify-envelope']) || flagEnabled(flags.noVerifyEnvelope)) return null;
1504
- const explicit = flags['verify-envelope'] || flags.verifyEnvelope || flags.envelope;
1505
- if (explicit !== undefined && explicit !== null && explicit !== true) return String(explicit).trim() || null;
1530
+ const explicit = explicitVerificationEnvelopeId(flags);
1531
+ if (explicit) return explicit;
1506
1532
  return context ? readActiveVerificationEnvelope(cwd, context) : readActiveVerificationEnvelope(cwd);
1507
1533
  }
1508
1534
 
@@ -1511,7 +1537,10 @@ function writeLocalVerificationEnvelope(cwd, envelope) {
1511
1537
  const paths = verificationPaths(cwd, envelope.envelopeId);
1512
1538
  ensureDir(paths.base);
1513
1539
  ensureDir(paths.packetsDir);
1514
- atomicWriteFile(paths.envelopeFile, JSON.stringify(envelope, null, 2));
1540
+ // Attach and status answer with a PACKETLESS header. Layer it over the mirror instead of replacing it, or the
1541
+ // offline fallback loses the packets and evaluation the last full read stored.
1542
+ const mirrored = Array.isArray(envelope.packets) ? envelope : Object.assign({}, readLocalVerificationEnvelope(cwd, envelope.envelopeId) || {}, envelope);
1543
+ atomicWriteFile(paths.envelopeFile, JSON.stringify(mirrored, null, 2));
1515
1544
  if (envelope.acceptance) {
1516
1545
  atomicWriteFile(paths.manifestFile, JSON.stringify(envelope.acceptance, null, 2));
1517
1546
  }
@@ -1682,27 +1711,133 @@ function syncEvidencePayload(sync) {
1682
1711
  return Object.assign({}, sync, { syncResults: trimmedResults });
1683
1712
  }
1684
1713
 
1685
- // The world an envelope was STARTED in, read the same way the platform reads it
1686
- // (VerificationEnvelopeService.envelopeStartWorld / ENVELOPE_START_WORLD_KEYS). Kept field-for-field identical so
1687
- // the attach-time warning and the verify-time wrong-world verdict can never disagree.
1688
- function envelopeWorldDrift(envelope, context = {}) {
1689
- if (!envelope || typeof envelope !== 'object') return [];
1690
- const source = envelope.source && typeof envelope.source === 'object' ? envelope.source : {};
1691
- const account = envelope.account && typeof envelope.account === 'object' ? envelope.account : {};
1692
- const started = {
1693
- 'repo account': account.repoAccountId != null ? String(account.repoAccountId) : (envelope.accountId != null ? String(envelope.accountId) : null),
1694
- 'data lane': normalizeDataMode(account.dataMode || source.dataMode) || null,
1695
- 'git branch': source.gitBranch || source.branchName || null,
1696
- workspace: normalizeWorkspace(source.workspace) || null
1697
- };
1698
- const now = {
1699
- 'repo account': context.accountId != null ? String(context.accountId) : null,
1700
- 'data lane': normalizeDataMode(context.dataMode) || null,
1701
- 'git branch': context.branchName || null,
1702
- workspace: normalizeWorkspace(context.workspace) || null
1703
- };
1704
- return Object.keys(started).filter((key) => started[key] && now[key] && started[key] !== now[key])
1705
- .map((key) => key + ' ' + started[key] + ' -> ' + now[key]);
1714
+ // The flag that would put a command into the envelope's world, per mismatched field. Rendering only: WHETHER a
1715
+ // field mismatches is the platform's answer (`compatibility`), never recomputed here.
1716
+ function verificationWorldHints(diffs = []) {
1717
+ return (Array.isArray(diffs) ? diffs : []).map((diff) => {
1718
+ const expected = diff && diff.expected;
1719
+ if (expected === undefined || expected === null || expected === '') return null;
1720
+ switch (diff.field) {
1721
+ case 'dataMode': return '--data-mode ' + expected;
1722
+ case 'workspace': return '--workspace ' + expected;
1723
+ case 'repoAccountId': return '--account-id ' + expected;
1724
+ case 'accountId': return '--as-account ' + expected;
1725
+ case 'componentBranch':
1726
+ case 'variantBranch': return '--variant-branch ' + expected;
1727
+ case 'gitBranch':
1728
+ case 'branchName': return 'run from git branch ' + expected;
1729
+ default: return null;
1730
+ }
1731
+ }).filter(Boolean);
1732
+ }
1733
+
1734
+ function verificationDiffSummary(diffs = []) {
1735
+ return (Array.isArray(diffs) ? diffs : []).map((diff) =>
1736
+ diff.field + ': envelope requires ' + diff.expected + ', this command has ' + (diff.command == null ? 'none (shared lane)' : diff.command)).join('; ');
1737
+ }
1738
+
1739
+ function verificationRefusalLines(result = {}, commandLabel = 'this command') {
1740
+ const lines = [result.message || ('Evidence from ' + commandLabel + ' would not count for verification envelope ' + result.envelopeId + '.')];
1741
+ const hints = verificationWorldHints(result.diffs);
1742
+ lines.push('Nothing ran. Pick one and re-run ' + commandLabel + ':');
1743
+ if (hints.length) lines.push(" - prove it in the envelope's world: " + hints.join(' '));
1744
+ lines.push(' - run it without recording evidence: --no-verify-envelope');
1745
+ lines.push(' - it is different work: remits-cli verify start --summary "..." (selects a new envelope for this checkout)');
1746
+ lines.push(' - record it anyway as context (reported wrong-world): --allow-wrong-world-evidence');
1747
+ return lines;
1748
+ }
1749
+
1750
+ function flagsWithoutVerificationEnvelope(flags = {}) {
1751
+ return Object.assign({}, flags, { 'no-verify-envelope': true });
1752
+ }
1753
+
1754
+ // Asks the platform - by the evaluator's own world rule - whether evidence from this command would count for the
1755
+ // selected envelope, BEFORE the command runs. The CLI used to answer this with its own copy of the rule, which had
1756
+ // drifted (shared lane vs a named workspace, the execution account, packet-type exemptions) and so blessed
1757
+ // evidence the report then rejected.
1758
+ //
1759
+ // `probe` is `{ packetType, world, test? }`, with world keys as a packet records them. Resolves to
1760
+ // `{ envelopeId, verdict, evidenceFlags }`: pass `evidenceFlags` to every appendVerificationPacket of the command.
1761
+ // attach - same world: evidence attaches to exactly this envelope (pinned by id, no second lookup).
1762
+ // refuse - a requirement could use this packet but the world differs: THROWS a refusal, nothing runs.
1763
+ // detach - differs, but nothing in the manifest could use it (or the envelope is closed): runs, attaches
1764
+ // nothing, and says so once. Refusing there would cost the agent a turn and protect nothing.
1765
+ // Idempotent within one invocation: a caller that already asked passes the answer in `__verificationPreflight`.
1766
+ async function verificationPreflight(api, cwd, session, accountId, flags = {}, probe = {}, options = {}) {
1767
+ if (flags.__verificationPreflight) return flags.__verificationPreflight;
1768
+ const label = options.command || 'this command';
1769
+ const context = activeVerificationContext(cwd, flags, session, accountId);
1770
+ const envelopeId = verificationEnvelopeIdForCommand(cwd, flags, context);
1771
+ if (!envelopeId) return { envelopeId: null, verdict: 'none', evidenceFlags: flags };
1772
+ const pinned = Object.assign({}, flags, { 'verify-envelope': envelopeId });
1773
+ let result;
1774
+ try {
1775
+ result = await postVerificationCommand(api, cwd, session, accountId, 'compatibility', { envelopeId, probe });
1776
+ } catch (err) {
1777
+ const status = err && err.response && err.response.status;
1778
+ if (status === 404) {
1779
+ if (explicitVerificationEnvelopeId(flags)) {
1780
+ const refusal = new Error('Verification envelope ' + envelopeId + ' does not exist on ' + context.baseUrl +
1781
+ '. Run `remits-cli verify list` to find the right id, or drop --verify-envelope.');
1782
+ refusal.refused = true;
1783
+ throw refusal;
1784
+ }
1785
+ console.error('Verification envelope ' + envelopeId + ' (selected for this checkout) no longer exists; evidence is not attached. ' +
1786
+ 'Run `remits-cli verify list`, then `verify use <id>` or `verify start`.');
1787
+ return { envelopeId: null, verdict: 'detach', evidenceFlags: flagsWithoutVerificationEnvelope(flags) };
1788
+ }
1789
+ // A platform without `compatibility`, or one that is unreachable: attach as before. The report still judges
1790
+ // the world; this only loses the early answer.
1791
+ return { envelopeId, verdict: 'unchecked', evidenceFlags: pinned };
1792
+ }
1793
+ const verdict = result.verdict === 'refuse' && options.neverRefuse ? 'detach' : (result.verdict || 'attach');
1794
+ const allowWrongWorld = flagEnabled(flags['allow-wrong-world-evidence']) || flagEnabled(flags.allowWrongWorldEvidence);
1795
+ if (verdict === 'refuse' && !allowWrongWorld) {
1796
+ const refusal = new Error(verificationRefusalLines(result, label).join('\n'));
1797
+ refusal.refused = true;
1798
+ refusal.compatibility = result;
1799
+ throw refusal;
1800
+ }
1801
+ if (verdict === 'refuse') {
1802
+ console.error('Attaching wrong-world evidence as context (--allow-wrong-world-evidence): ' + result.message);
1803
+ return { envelopeId, verdict, compatibility: result, evidenceFlags: pinned };
1804
+ }
1805
+ if (verdict === 'detach') {
1806
+ if (!options.quiet && result.message) {
1807
+ const hints = verificationWorldHints(result.diffs);
1808
+ console.error('Verification: ' + result.message + (hints.length ? ' (It would attach, as context only, with: ' + hints.join(' ') + ')' : ''));
1809
+ }
1810
+ return { envelopeId: null, verdict, compatibility: result, evidenceFlags: flagsWithoutVerificationEnvelope(flags) };
1811
+ }
1812
+ return { envelopeId, verdict, compatibility: result, evidenceFlags: pinned };
1813
+ }
1814
+
1815
+ // "Would a plain `test run` from this checkout count for this envelope?" - printed where an agent selects or
1816
+ // inspects an envelope, so a mismatch is learned before the first run rather than from a refusal after it.
1817
+ async function printVerificationRunProbe(api, cwd, session, accountId, envelopeId, world = {}, options = {}) {
1818
+ if (!envelopeId) return null;
1819
+ let result;
1820
+ try {
1821
+ result = await postVerificationCommand(api, cwd, session, accountId, 'compatibility', {
1822
+ envelopeId,
1823
+ probe: { packetType: 'test_run', world }
1824
+ });
1825
+ } catch (_) {
1826
+ return null;
1827
+ }
1828
+ if (options.onlyMismatch && result.verdict === 'attach') return result;
1829
+ if (result.comparisonWorld) {
1830
+ console.log('Proof world (' + (result.comparisonWorldSource || 'unknown') + '):', JSON.stringify(result.comparisonWorld));
1831
+ }
1832
+ if (result.verdict === 'attach') {
1833
+ console.log('A `test run` from this checkout (dataMode=' + (world.dataMode || DEFAULT_DATA_MODE) + ') attaches to this envelope.');
1834
+ } else if (result.verdict === 'refuse' || result.verdict === 'detach') {
1835
+ const hints = verificationWorldHints(result.diffs);
1836
+ console.log('A `test run` from this checkout (dataMode=' + (world.dataMode || DEFAULT_DATA_MODE) + ') would ' +
1837
+ (result.verdict === 'refuse' ? 'be REFUSED' : 'NOT attach') + ' - ' + verificationDiffSummary(result.diffs) + '.');
1838
+ if (hints.length) console.log(' to prove it for this envelope, pass: ' + hints.join(' '));
1839
+ }
1840
+ return result;
1706
1841
  }
1707
1842
 
1708
1843
  async function appendVerificationPacket(api, cwd, session, accountId, flags, packet, options = {}) {
@@ -1720,18 +1855,20 @@ async function appendVerificationPacket(api, cwd, session, accountId, flags, pac
1720
1855
  try {
1721
1856
  const response = await postVerificationCommand(api, cwd, session, accountId, 'packet', {
1722
1857
  envelopeId,
1723
- packet: finalPacket
1858
+ packet: finalPacket,
1859
+ // Packetless answer: this runs on every command while an envelope is active.
1860
+ compact: true
1724
1861
  });
1725
1862
  if (response.packet) writeLocalVerificationPacket(cwd, envelopeId, response.packet);
1726
1863
  if (response.envelope) writeLocalVerificationEnvelope(cwd, response.envelope);
1727
1864
  if (!options.quiet) {
1728
1865
  console.log('Verification envelope:', envelopeId, '(packet ' + finalPacket.type + ' attached)');
1729
- // Say it HERE, not only at `verify` time. Evidence attached to an envelope started in a different world
1730
- // is evidence about a different world, and the reader has to learn that while they can still redo the run.
1731
- const drift = envelopeWorldDrift(response.envelope, context);
1732
- if (drift.length) {
1733
- console.log(' WARNING: this envelope was started in a different world - ' + drift.join('; ') +
1734
- '. The packet is attached, but it will be reported wrong-world at verify time.');
1866
+ // The platform's own verdict on the stored packet, so this line and `verify report` cannot disagree.
1867
+ const diffs = Array.isArray(response.worldDiffs) ? response.worldDiffs : [];
1868
+ if (diffs.length) {
1869
+ console.log(' WARNING: this packet is from a different world than the envelope requires - ' +
1870
+ diffs.map((diff) => diff.field + ' expected ' + diff.expected + ' got ' + (diff.packet == null ? 'none' : diff.packet)).join('; ') +
1871
+ '. It is attached, but it cannot satisfy a requirement.');
1735
1872
  }
1736
1873
  }
1737
1874
  return response;
@@ -1797,6 +1934,106 @@ function printVerificationEnvelope(envelope, fallbackEnvelopeId) {
1797
1934
  }
1798
1935
  }
1799
1936
 
1937
+ function slimVerificationPacket(packet) {
1938
+ const p = packet || {};
1939
+ const world = p.world || {};
1940
+ return {
1941
+ packetId: p.packetId || p.id || null,
1942
+ type: p.type || 'unknown',
1943
+ success: p.success !== false,
1944
+ stale: Boolean(p.stale),
1945
+ claim: p.claim || p.summary || null,
1946
+ taskId: p.taskId || (p.testRun && p.testRun.taskId) || null,
1947
+ testName: p.testName || (p.testRun && p.testRun.testName) || null,
1948
+ accountId: world.accountId || p.accountId || null,
1949
+ dataMode: world.dataMode || p.dataMode || null,
1950
+ branchName: world.branchName || p.branchName || null,
1951
+ workspace: world.workspace || p.workspace || null,
1952
+ recordedAt: formatTime(p.recordedAt || p.createdAt || p.at || p.timestamp),
1953
+ message: p.message || p.error || null
1954
+ };
1955
+ }
1956
+
1957
+ function compactVerificationEnvelope(envelope, fallbackEnvelopeId) {
1958
+ const env = envelope || {};
1959
+ const evaluation = env.evaluation || {};
1960
+ const packets = Array.isArray(env.packets) ? env.packets : [];
1961
+ const failures = Array.isArray(evaluation.currentFailures)
1962
+ ? evaluation.currentFailures
1963
+ : packets.filter((packet) => packet && packet.success === false && !packet.stale);
1964
+ const stale = Array.isArray(evaluation.stale) ? evaluation.stale : packets.filter((packet) => packet && packet.stale);
1965
+ const requiredEvidence = ((env.acceptance || {}).requiredEvidence || evaluation.requiredEvidence || []);
1966
+ const latestByType = evaluation.latestByType || env.latestByType || {};
1967
+ const nextActions = [];
1968
+
1969
+ if (evaluation.noRequiredEvidence) {
1970
+ nextActions.push('Attach a manifest or required evidence contract; packets are context, not proof of completion.');
1971
+ }
1972
+ if (failures.length) {
1973
+ nextActions.push('Inspect and replace the current failing evidence packet(s).');
1974
+ }
1975
+ if (stale.length) {
1976
+ nextActions.push('Re-run stale evidence after the requirement/source change.');
1977
+ }
1978
+
1979
+ return {
1980
+ envelopeId: env.envelopeId || fallbackEnvelopeId || null,
1981
+ summary: env.summary || null,
1982
+ status: env.status || 'unknown',
1983
+ health: evaluation.health || null,
1984
+ acceptanceStatus: evaluation.acceptanceStatus || env.acceptanceStatus || null,
1985
+ requiredEvidence: {
1986
+ satisfiedCount: evaluation.satisfiedCount || 0,
1987
+ requiredCount: evaluation.requiredCount || requiredEvidence.length || 0,
1988
+ missingCount: evaluation.missingCount || 0,
1989
+ staleCount: evaluation.staleCount || stale.length || 0,
1990
+ noRequiredEvidence: Boolean(evaluation.noRequiredEvidence),
1991
+ declared: requiredEvidence.slice(0, 10).map((entry) => ({
1992
+ type: entry && entry.type || entry && entry.evidenceType || null,
1993
+ category: entry && entry.category || null,
1994
+ claim: entry && entry.claim || entry && entry.description || null
1995
+ }))
1996
+ },
1997
+ packets: {
1998
+ count: packets.length,
1999
+ failedCount: evaluation.failedCount || failures.length || 0,
2000
+ currentFailureCount: evaluation.currentFailureCount || failures.length || 0,
2001
+ lastPacketAt: formatTime(env.lastPacketAtMs || env.latestPacketAtMs || env.updatedAtMs),
2002
+ recent: packets.slice(-5).map(slimVerificationPacket)
2003
+ },
2004
+ latestByType,
2005
+ currentFailures: failures.slice(0, 10).map(slimVerificationPacket),
2006
+ stale: stale.slice(0, 10).map(slimVerificationPacket),
2007
+ warnings: (env.warnings || []).slice(0, 10),
2008
+ nextActions
2009
+ };
2010
+ }
2011
+
2012
+ function printCompactVerificationEnvelope(compact) {
2013
+ console.log('Verification envelope:', compact.envelopeId || '(no id)');
2014
+ if (compact.summary) console.log('Summary:', compact.summary);
2015
+ console.log('Status:', compact.status + (compact.health ? ' health=' + compact.health : ''));
2016
+ if (compact.requiredEvidence.noRequiredEvidence) {
2017
+ console.log('Required evidence: none declared');
2018
+ } else {
2019
+ console.log('Required evidence:', compact.requiredEvidence.satisfiedCount + '/' + compact.requiredEvidence.requiredCount +
2020
+ (compact.requiredEvidence.staleCount ? ' stale=' + compact.requiredEvidence.staleCount : ''));
2021
+ }
2022
+ console.log('Packets:', compact.packets.count + ' current failures=' + compact.packets.currentFailureCount);
2023
+ if (compact.currentFailures.length) {
2024
+ console.log('Current failures:');
2025
+ compact.currentFailures.forEach((packet) => {
2026
+ console.log(' - ' + packet.type + ' ' + (packet.packetId || '') + (packet.claim ? ' - ' + packet.claim : '') +
2027
+ (packet.message ? ' (' + packet.message + ')' : ''));
2028
+ });
2029
+ }
2030
+ if (compact.nextActions.length) {
2031
+ console.log('Next actions:');
2032
+ compact.nextActions.forEach((action) => console.log(' - ' + action));
2033
+ }
2034
+ console.log('Full envelope: remits-cli verify show --json');
2035
+ }
2036
+
1800
2037
  function printAcceptanceWarnings(envelope) {
1801
2038
  const warnings = envelope && envelope.acceptance && Array.isArray(envelope.acceptance.warnings)
1802
2039
  ? envelope.acceptance.warnings
@@ -2672,6 +2909,42 @@ function resolveSessionContext(cwd, flags) {
2672
2909
  };
2673
2910
  }
2674
2911
 
2912
+ function hasExplicitAccountIdFlag(flags) {
2913
+ const value = Number(flags && flags['account-id']);
2914
+ return Number.isFinite(value) && value > 0;
2915
+ }
2916
+
2917
+ function platformRepoEntryMatchesCwd(cwd) {
2918
+ const entry = getPlatformRepoEntry();
2919
+ if (!entry || !entry.directory || !cwd) return false;
2920
+ try {
2921
+ return path.resolve(entry.directory) === path.resolve(cwd);
2922
+ } catch (_) {
2923
+ return false;
2924
+ }
2925
+ }
2926
+
2927
+ function platformRepoAccountCommandProblem(cwd, flags, commandName, sessionContext) {
2928
+ if (hasExplicitAccountIdFlag(flags)) return null;
2929
+ try {
2930
+ if (loadAccountInfo(cwd)) return null;
2931
+ } catch (_) {
2932
+ // No account-info.json means the command would fall back to session state.
2933
+ }
2934
+ if (!looksLikePlatformRepo(cwd) && !platformRepoEntryMatchesCwd(cwd)) return null;
2935
+
2936
+ const account = sessionContext && sessionContext.accountId
2937
+ ? 'account ' + sessionContext.accountId
2938
+ : 'the last authenticated account';
2939
+ return 'Refusing ' + commandName + ' from the Remits platform repo. This checkout is back-stage platform source, not an account repository, so the command would target ' +
2940
+ account + ' from session state. Run it from that account repo, or pass --account-id <id> explicitly if you really intend to inspect that account from here.';
2941
+ }
2942
+
2943
+ function requireAccountRepoContext(cwd, flags, commandName, sessionContext) {
2944
+ const problem = platformRepoAccountCommandProblem(cwd, flags, commandName, sessionContext);
2945
+ if (problem) throw new Error(problem);
2946
+ }
2947
+
2675
2948
  /*
2676
2949
  ## Component Discovery And HTTP Logging
2677
2950
  */
@@ -3755,6 +4028,7 @@ async function pushComponentsCommand(flags) {
3755
4028
  const cwd = process.cwd();
3756
4029
  ensureLocalState(cwd);
3757
4030
  const sessionContext = resolveSessionContext(cwd, flags);
4031
+ requireAccountRepoContext(cwd, flags, 'components stage', sessionContext);
3758
4032
  const { session, accountId } = sessionContext;
3759
4033
  const baseUrl = flags['base-url'] || session.baseUrl || DEFAULT_BASE_URL;
3760
4034
  const branchName = flags.branch || currentBranch(cwd);
@@ -3876,6 +4150,10 @@ async function pushComponentsCommand(flags) {
3876
4150
  // or, worse, conclude staging was broken and go on reading trunk. Scaled by payload size, floored at
3877
4151
  // the old default, and overridable.
3878
4152
  const api = buildAxios(baseUrl, session.token, stageTimeoutMs(components, flags));
4153
+ const verification = await verificationPreflight(api, cwd, session, accountId, flags, {
4154
+ packetType: 'stage',
4155
+ world: { accountId, dataMode, branchName, workspace }
4156
+ }, { command: 'components stage' });
3879
4157
  const stagePayload = {
3880
4158
  token: session.token,
3881
4159
  accountId,
@@ -3919,7 +4197,7 @@ async function pushComponentsCommand(flags) {
3919
4197
  // the platform rejected it. The packet is best-effort: a failing stage must surface the STAGE error,
3920
4198
  // never an error from writing the record of it.
3921
4199
  try {
3922
- await appendVerificationPacket(api, cwd, session, accountId, flags, {
4200
+ await appendVerificationPacket(api, cwd, session, accountId, verification.evidenceFlags, {
3923
4201
  type: 'stage',
3924
4202
  success: false,
3925
4203
  claim: 'Components refused at staging',
@@ -3956,7 +4234,7 @@ async function pushComponentsCommand(flags) {
3956
4234
  throw new Error(response.message || 'Server stage failed');
3957
4235
  }
3958
4236
 
3959
- await appendVerificationPacket(api, cwd, session, accountId, flags, {
4237
+ await appendVerificationPacket(api, cwd, session, accountId, verification.evidenceFlags, {
3960
4238
  type: 'stage',
3961
4239
  success: response.success !== false,
3962
4240
  claim: 'Components staged for verification',
@@ -4682,7 +4960,8 @@ function printBranchContext(response) {
4682
4960
  console.log(' commit writes: ' + ctx.commitWrites);
4683
4961
  if (Object.prototype.hasOwnProperty.call(ctx, 'lastSyncedSha')) {
4684
4962
  // null is "unknown" (never synced, expired, or the last sync had errors) — never "nothing landed".
4685
- console.log(' last platform sync of this branch: ' + (ctx.lastSyncedSha ? short(ctx.lastSyncedSha) : 'unknown'));
4963
+ const source = ctx.lastSyncedShaSource ? ' [' + ctx.lastSyncedShaSource + ']' : '';
4964
+ console.log(' last platform sync of this branch: ' + (ctx.lastSyncedSha ? short(ctx.lastSyncedSha) : 'unknown') + source);
4686
4965
  }
4687
4966
  if (ctx.repository) {
4688
4967
  console.log(' sync reads repository: ' + ctx.repository +
@@ -4694,6 +4973,8 @@ function printBranchContext(response) {
4694
4973
  ('this account resolves the branch through an ancestor owned by account ' + ctx.resolvingOwnerAccountId + '.')));
4695
4974
  }
4696
4975
 
4976
+ variantBranchIdentityLines(response).forEach((line) => console.log(line));
4977
+
4697
4978
  if (!ctx.onTrunk) {
4698
4979
  console.log(' variants stored on this branch: ' + (ctx.variantCount || 0));
4699
4980
  const subs = ctx.subscribers || [];
@@ -4707,6 +4988,29 @@ function printBranchContext(response) {
4707
4988
  console.log('');
4708
4989
  }
4709
4990
 
4991
+ // A variant branch whose checkout identifies as its OWNER (account-info.json names the owner) runs every command as
4992
+ // the owner, and the owner subscribes to nothing — so a feature branch cut from this checkout resolves TRUNK, and no
4993
+ // run exercises what the subscriber actually gets. With exactly one subscriber that identity is repairable, and the
4994
+ // agent should hear it here rather than infer it from a page that rendered trunk. Pure.
4995
+ function variantBranchIdentityLines(response) {
4996
+ const ctx = response && response.branchContext;
4997
+ if (!ctx || ctx.onTrunk || ctx.variantBranchSource === 'subscription-fallback') return [];
4998
+ const subs = Array.isArray(ctx.subscribers) ? ctx.subscribers : [];
4999
+ const checkoutAccountId = response.accountId;
5000
+ if (subs.length !== 1 || checkoutAccountId == null || String(subs[0].accountId) === String(checkoutAccountId)) return [];
5001
+ if (ctx.commitOwnerAccountId != null && String(ctx.commitOwnerAccountId) !== String(checkoutAccountId)) return [];
5002
+ const sub = subs[0];
5003
+ const label = sub.accountId + (sub.accountName ? ' (' + sub.accountName + ')' : '');
5004
+ return [
5005
+ '',
5006
+ ' CHECKOUT IDENTITY: this branch has one subscriber, account ' + label + ', but this checkout runs as the owner,',
5007
+ ' account ' + checkoutAccountId + ' (account-info.json names it). Feature branches cut from here run as ' + checkoutAccountId +
5008
+ ' too, which subscribes to nothing, so they resolve trunk.',
5009
+ ' run as the subscriber: --as-account ' + sub.accountId,
5010
+ ' repair the identity: remits-cli components sync --account-id ' + sub.accountId + ' then git pull --ff-only'
5011
+ ];
5012
+ }
5013
+
4710
5014
  function printClearSummary(response, flags) {
4711
5015
  console.log('Cleared keys:', response.clearedCount || 0);
4712
5016
  console.log('Remaining staged count:', response.remainingCount || 0);
@@ -4848,6 +5152,7 @@ async function statusComponentsCommand(flags) {
4848
5152
  const cwd = process.cwd();
4849
5153
  ensureLocalState(cwd);
4850
5154
  const sessionContext = resolveSessionContext(cwd, flags);
5155
+ requireAccountRepoContext(cwd, flags, 'components status', sessionContext);
4851
5156
  const { session, accountId } = sessionContext;
4852
5157
  const baseUrl = flags['base-url'] || session.baseUrl || DEFAULT_BASE_URL;
4853
5158
  const branchName = flags.branch || currentBranch(cwd);
@@ -4916,6 +5221,7 @@ async function lanesComponentsCommand(flags) {
4916
5221
  const cwd = process.cwd();
4917
5222
  ensureLocalState(cwd);
4918
5223
  const sessionContext = resolveSessionContext(cwd, flags);
5224
+ requireAccountRepoContext(cwd, flags, 'components lanes', sessionContext);
4919
5225
  const { session, accountId } = sessionContext;
4920
5226
  const baseUrl = flags['base-url'] || session.baseUrl || DEFAULT_BASE_URL;
4921
5227
  const branchName = flags.branch || currentBranch(cwd);
@@ -5007,6 +5313,7 @@ async function entriesComponentsCommand(flags) {
5007
5313
  const cwd = process.cwd();
5008
5314
  ensureLocalState(cwd);
5009
5315
  const sessionContext = resolveSessionContext(cwd, flags);
5316
+ requireAccountRepoContext(cwd, flags, 'components entries', sessionContext);
5010
5317
  const { session, accountId } = sessionContext;
5011
5318
  const baseUrl = flags['base-url'] || session.baseUrl || DEFAULT_BASE_URL;
5012
5319
  const branchName = flags.branch || currentBranch(cwd);
@@ -5050,6 +5357,7 @@ async function clearComponentsCommand(flags) {
5050
5357
  const cwd = process.cwd();
5051
5358
  ensureLocalState(cwd);
5052
5359
  const sessionContext = resolveSessionContext(cwd, flags);
5360
+ requireAccountRepoContext(cwd, flags, 'components clear', sessionContext);
5053
5361
  const { session, accountId } = sessionContext;
5054
5362
  const baseUrl = flags['base-url'] || session.baseUrl || DEFAULT_BASE_URL;
5055
5363
  const branchName = flags.branch || currentBranch(cwd);
@@ -5109,6 +5417,7 @@ async function syncComponentsCommand(rawFlags) {
5109
5417
  const flags = safe ? expandSafeSyncFlags(rawFlags) : rawFlags;
5110
5418
  ensureLocalState(cwd);
5111
5419
  const sessionContext = resolveSessionContext(cwd, flags);
5420
+ requireAccountRepoContext(cwd, flags, 'components sync', sessionContext);
5112
5421
  const { session, accountId } = sessionContext;
5113
5422
  const baseUrl = flags['base-url'] || session.baseUrl || DEFAULT_BASE_URL;
5114
5423
  const branchName = flags.branch || currentBranch(cwd);
@@ -5123,6 +5432,11 @@ async function syncComponentsCommand(rawFlags) {
5123
5432
  const preflightRequested = syncPreflightRequested(flags);
5124
5433
  const namesOnly = flagEnabled(flags['names-only']) || flagEnabled(flags.namesOnly);
5125
5434
 
5435
+ const verification = await verificationPreflight(api, cwd, session, accountId, flags, {
5436
+ packetType: dryRun || namesOnly ? 'sync_dry_run' : 'sync_mutation',
5437
+ world: { accountId, dataMode, branchName, workspace }
5438
+ }, { command: 'components sync' });
5439
+
5126
5440
  printProdDataBanner({
5127
5441
  dataMode,
5128
5442
  accountId,
@@ -5334,7 +5648,7 @@ async function syncComponentsCommand(rawFlags) {
5334
5648
  summary.gates = gate.checks;
5335
5649
  summary.gateViolations = gate.violations;
5336
5650
 
5337
- await appendVerificationPacket(api, cwd, session, accountId, flags, {
5651
+ await appendVerificationPacket(api, cwd, session, accountId, verification.evidenceFlags, {
5338
5652
  type: response.sync && response.sync.dryRun ? 'sync_dry_run' : 'sync_mutation',
5339
5653
  success: response.success !== false,
5340
5654
  claim: response.sync && response.sync.dryRun ? 'Component sync dry-run observed' : 'Component sync completed',
@@ -6211,6 +6525,7 @@ async function branchesComponentsCommand(flags) {
6211
6525
  const cwd = process.cwd();
6212
6526
  ensureLocalState(cwd);
6213
6527
  const sessionContext = resolveSessionContext(cwd, flags);
6528
+ requireAccountRepoContext(cwd, flags, 'components branches', sessionContext);
6214
6529
  const { session, accountId } = sessionContext;
6215
6530
  const baseUrl = flags['base-url'] || session.baseUrl || DEFAULT_BASE_URL;
6216
6531
  const api = buildAxios(baseUrl, session.token);
@@ -6408,6 +6723,7 @@ async function promotionComponentsCommand(flags) {
6408
6723
  const cwd = process.cwd();
6409
6724
  ensureLocalState(cwd);
6410
6725
  const sessionContext = resolvePromotionSessionContext(cwd, flags);
6726
+ requireAccountRepoContext(cwd, flags, 'components promotion', sessionContext);
6411
6727
  const { session, accountId } = sessionContext;
6412
6728
  const baseUrl = flags['base-url'] || session.baseUrl || DEFAULT_BASE_URL;
6413
6729
  const api = buildAxios(baseUrl, session.token);
@@ -6573,7 +6889,9 @@ function shortSignature(signature) {
6573
6889
  async function commitComponentsCommand(flags) {
6574
6890
  const cwd = process.cwd();
6575
6891
  ensureLocalState(cwd);
6576
- const { accountId } = resolveSessionContext(cwd, flags);
6892
+ const sessionContext = resolveSessionContext(cwd, flags);
6893
+ requireAccountRepoContext(cwd, flags, 'components commit', sessionContext);
6894
+ const { session, accountId } = sessionContext;
6577
6895
  const branchName = flags.branch || currentBranch(cwd);
6578
6896
  const commitMessage = String(flags.message || ('remits-cli commit sync ' + new Date().toISOString()));
6579
6897
  const allowEmpty = flags['allow-empty'] === true || flags['allow-empty'] === 'true';
@@ -6586,6 +6904,15 @@ async function commitComponentsCommand(flags) {
6586
6904
  if (await refuseUnsafeTrunkCommitBeforeGit(flags, accountId, branchName, skipGit)) {
6587
6905
  return;
6588
6906
  }
6907
+ // The sync's verification answer, asked BEFORE `git push`. Asked inside the sync it could refuse only after the
6908
+ // push, leaving a pushed-but-unsynced branch - the one state this command exists to avoid. The sync below
6909
+ // reuses this answer instead of asking again.
6910
+ const commitVerification = await verificationPreflight(
6911
+ buildAxios(flags['base-url'] || session.baseUrl || DEFAULT_BASE_URL, session.token),
6912
+ cwd, session, accountId, Object.assign({}, flags, { branch: branchName }), {
6913
+ packetType: 'sync_mutation',
6914
+ world: { accountId, dataMode: resolveDataMode(flags, session), branchName, workspace: resolveWorkspace(cwd, flags) }
6915
+ }, { command: 'components commit' });
6589
6916
 
6590
6917
  // Landing is serial and nothing can make it concurrent: `git add -A` sweeps a shared checkout, and the
6591
6918
  // platform pushes a regenerated `account-info.json` back to the branch during sync, so two commits
@@ -6681,7 +7008,8 @@ async function commitComponentsCommand(flags) {
6681
7008
  const syncResponse = await withStdoutRoutedToStderr(jsonOutput, () => syncComponentsCommand({
6682
7009
  ...flags,
6683
7010
  branch: branchName,
6684
- 'account-id': accountId
7011
+ 'account-id': accountId,
7012
+ __verificationPreflight: commitVerification
6685
7013
  }));
6686
7014
  outcome.sync = syncResponse && flagEnabled(flags.summary) ? buildSyncSummary(syncResponse) : (syncResponse || null);
6687
7015
  // Stop here when the sync did not succeed. In --json mode a refused sync gate RETURNS (with a non-zero
@@ -7291,8 +7619,17 @@ function runWorldLines(world, context = {}) {
7291
7619
  if (world.componentOwnerAccountId && world.componentOwnerAccountId !== world.executionAccountId) accounts.push('component owner ' + world.componentOwnerAccountId);
7292
7620
  lines.push(' runs as: ' + accounts.join(', '));
7293
7621
  lines.push(' data lane: ' + (world.dataMode || context.dataMode || 'unknown'));
7294
- const branchSource = world.variantBranchSource ? ' [' + world.variantBranchSource + ']' : '';
7295
- lines.push(' component world: ' + (world.resolves || (world.componentBranch ? "trunk + the '" + world.componentBranch + "' variant overlays" : 'trunk')) + branchSource);
7622
+ const contextVariantWorld = context.variantWorld && typeof context.variantWorld === 'object' ? context.variantWorld : {};
7623
+ const subscribedComponentBranch = world.subscribedComponentBranch || contextVariantWorld.subscribedComponentBranch;
7624
+ const componentBranch = world.componentBranch || subscribedComponentBranch || contextVariantWorld.componentBranch || world.variantBranch;
7625
+ const branchSource = componentBranch && subscribedComponentBranch && String(componentBranch) === String(subscribedComponentBranch)
7626
+ ? 'subscription'
7627
+ : (world.componentBranchSource || contextVariantWorld.componentBranchSource || world.variantBranchSource || contextVariantWorld.variantBranchSource);
7628
+ const branchLabel = componentBranch
7629
+ ? "componentBranch=" + componentBranch + (branchSource ? ' via ' + branchSource : '')
7630
+ : (branchSource || '');
7631
+ const branchSuffix = branchLabel ? ' [' + branchLabel + ']' : '';
7632
+ lines.push(' component world: ' + (world.resolves || (componentBranch ? "trunk + the '" + componentBranch + "' variant overlays" : 'trunk')) + branchSuffix);
7296
7633
  const laneParts = [world.stagingLane || (world.branchName ? world.branchName + (world.workspace ? ' [ws:' + world.workspace + ']' : '') : 'none')];
7297
7634
  if (world.sharedLane) laneParts.push('(shared lane)');
7298
7635
  if (world.laneContentHash) laneParts.push('content ' + String(world.laneContentHash).slice(0, 12));
@@ -7312,6 +7649,24 @@ function runWorldLines(world, context = {}) {
7312
7649
  lines.push(' test component: ' + world.testComponentSource + (world.testComponentSignature ? ' ' + shortSignature(world.testComponentSignature) : '') +
7313
7650
  (world.testComponentBranch ? " variant '" + world.testComponentBranch + "'" : ''));
7314
7651
  }
7652
+ lines.push(...stagedMaskedByVariantLines(world.stagedMaskedByVariant, world.executionAccountId || context.accountId));
7653
+ return lines;
7654
+ }
7655
+
7656
+ // Staged edits from a TRUNK lane that the execution account will not run, because its branch overrides those
7657
+ // components with committed variants. Production behaves the same way (the variant keeps shadowing trunk), so this
7658
+ // is drift to report, not a bug to route around. Pure.
7659
+ function stagedMaskedByVariantLines(masked, accountId) {
7660
+ if (!Array.isArray(masked) || !masked.length) return [];
7661
+ const lines = [' NOT RUN FOR THIS ACCOUNT: ' + masked.length + ' staged trunk edit(s) are shadowed by account ' +
7662
+ (accountId || '?') + "'s committed variant, exactly as in production:"];
7663
+ masked.slice(0, 10).forEach((entry) => {
7664
+ lines.push(' - ' + entry.type + ' ' + (entry.componentId != null ? entry.componentId + ' ' : '') + (entry.componentName || '') +
7665
+ " (variant " + (entry.variantId || '?') + " on '" + entry.branch + "')");
7666
+ });
7667
+ if (masked.length > 10) lines.push(' ...' + (masked.length - 10) + ' more');
7668
+ lines.push(" To change what this account runs, edit the component on its variant branch ('" + masked[0].branch +
7669
+ "') or promote the trunk change to that branch.");
7315
7670
  return lines;
7316
7671
  }
7317
7672
 
@@ -7517,6 +7872,22 @@ async function testCommand(flags) {
7517
7872
  // --variant-branch explicitly probes a committed variant branch. Normally omitted: variants resolve
7518
7873
  // from the account's subscription edge, which is what production does.
7519
7874
  const variantBranch = flags['variant-branch'];
7875
+ // Asked BEFORE the run: a run whose evidence could only fail the envelope's contract is refused here, not
7876
+ // discovered at `verify report` after the minutes it took.
7877
+ const numericTestRef = /^\d+$/.test(String(testRef));
7878
+ const verification = await verificationPreflight(api, cwd, session, accountId, flags, {
7879
+ packetType: 'test_run',
7880
+ world: {
7881
+ accountId,
7882
+ executionAccountId: asAccountId ? Number(asAccountId) : Number(accountId),
7883
+ dataMode,
7884
+ branchName,
7885
+ workspace,
7886
+ componentBranch: variantBranch || undefined
7887
+ },
7888
+ test: numericTestRef ? { testId: Number(testRef) } : { testName: String(testRef) }
7889
+ }, { command: 'test run' });
7890
+ const activeEnvelopeId = verification.envelopeId;
7520
7891
 
7521
7892
  withStdoutRoutedToStderr(jsonOutput, () => {
7522
7893
  printProdDataBanner({
@@ -7561,6 +7932,7 @@ async function testCommand(flags) {
7561
7932
  host: normalizeBaseUrl(baseUrl),
7562
7933
  accountId: asAccountId || accountId,
7563
7934
  dataMode,
7935
+ variantWorld: start.variantWorld,
7564
7936
  gitHead: safeGitValue(cwd, 'git rev-parse HEAD')
7565
7937
  }).forEach((line) => console.log(line));
7566
7938
  console.log('Test run started:', start.taskId);
@@ -7615,8 +7987,6 @@ async function testCommand(flags) {
7615
7987
  process.exitCode = 1;
7616
7988
  }
7617
7989
 
7618
- const verificationContext = activeVerificationContext(cwd, flags, session, accountId);
7619
- const activeEnvelopeId = verificationEnvelopeIdForCommand(cwd, flags, verificationContext);
7620
7990
  const componentProvenance = testComponentProvenance(status);
7621
7991
  const sourceRevision = collectVerificationSource(cwd, flags);
7622
7992
  const priorPackets = await readVerificationPacketsForDiagnostics(api, cwd, session, accountId, activeEnvelopeId, {
@@ -7636,7 +8006,7 @@ async function testCommand(flags) {
7636
8006
  });
7637
8007
  }
7638
8008
 
7639
- await appendVerificationPacket(api, cwd, session, accountId, flags, {
8009
+ await appendVerificationPacket(api, cwd, session, accountId, verification.evidenceFlags, {
7640
8010
  type: 'test_run',
7641
8011
  success: status.status === 'completed' && !(status.result && status.result.failed > 0) && !unmatched.length,
7642
8012
  claim: 'Test run ' + String(testRef),
@@ -7834,6 +8204,18 @@ async function tokenCommand(flags) {
7834
8204
  // Embeddable resolves that account's branch variants rather than the owner's trunk.
7835
8205
  // --variant-branch explicitly probes a committed variant branch before any edge subscribes.
7836
8206
  const variantBranch = flags['variant-branch'];
8207
+ const tokenAsAccountId = flags['as-account'] || flags['as-account-id'];
8208
+ const verification = await verificationPreflight(api, cwd, session, accountId, flags, {
8209
+ packetType: 'token_inspect',
8210
+ world: {
8211
+ accountId,
8212
+ executionAccountId: tokenAsAccountId ? Number(tokenAsAccountId) : Number(accountId),
8213
+ dataMode,
8214
+ branchName,
8215
+ workspace,
8216
+ componentBranch: variantBranch || undefined
8217
+ }
8218
+ }, { command: 'token' });
7837
8219
  // Send the path so the server can also mint the EMBEDDABLE-SCOPED token key. `tokenKey` opens a
7838
8220
  // tokenized URL in a browser; `embedTokenKey` lets the `<script>` embed loader resolve the component
7839
8221
  // from persisted token context because the loader request sends no path.
@@ -7889,6 +8271,11 @@ async function tokenCommand(flags) {
7889
8271
  if (data.resolution) {
7890
8272
  output.resolution = data.resolution;
7891
8273
  }
8274
+ if (Array.isArray(data.stagedMaskedByVariant) && data.stagedMaskedByVariant.length) {
8275
+ output.stagedMaskedByVariant = data.stagedMaskedByVariant;
8276
+ // stderr: stdout is the JSON document. Said before the URL is opened, not discovered from what renders.
8277
+ stagedMaskedByVariantLines(data.stagedMaskedByVariant, data.accountId).forEach((line) => console.error(line.replace(/^ /, '')));
8278
+ }
7892
8279
 
7893
8280
  // Present only when the path resolved to an Embeddable. `embedTokenKey` is what a host site pastes
7894
8281
  // into the loader snippet; the render shape is echoed because it decides what that host receives.
@@ -7909,7 +8296,7 @@ async function tokenCommand(flags) {
7909
8296
  }
7910
8297
  console.log(JSON.stringify(output, null, 2));
7911
8298
 
7912
- await appendVerificationPacket(api, cwd, session, accountId, flags, {
8299
+ await appendVerificationPacket(api, cwd, session, accountId, verification.evidenceFlags, {
7913
8300
  type: 'token_inspect',
7914
8301
  success: true,
7915
8302
  claim: 'Verification token minted',
@@ -8131,7 +8518,12 @@ async function toolCommand(flags) {
8131
8518
  data.toolMessage = data.toolMessage || toolResultMessage(data.result);
8132
8519
  }
8133
8520
  if (data.status === 'failed' || polledFailed) process.exitCode = 1;
8134
- await appendVerificationPacket(api, cwd, session, accountId, flags, {
8521
+ // Same question as the call that started it: a poll must not attach where the call itself would not.
8522
+ const statusVerification = await verificationPreflight(api, cwd, session, accountId, flags, {
8523
+ packetType: 'tool_call',
8524
+ world: { accountId: statusAccountId, dataMode: data.dataMode || dataMode, branchName: flags.branch || (stored && stored.branchName) || branchName, workspace: resolveWorkspace(cwd, flags) }
8525
+ }, { command: 'tool status', neverRefuse: true, quiet: jsonOutput });
8526
+ await appendVerificationPacket(api, cwd, session, accountId, statusVerification.evidenceFlags, {
8135
8527
  type: 'tool_call',
8136
8528
  success: data.status !== 'failed' && !polledFailed,
8137
8529
  claim: 'Tool status ' + requestedCallId,
@@ -8171,6 +8563,12 @@ async function toolCommand(flags) {
8171
8563
  stderr: jsonOutput
8172
8564
  });
8173
8565
 
8566
+ const toolWorkspace = resolveWorkspace(cwd, flags);
8567
+ const verification = await verificationPreflight(api, cwd, session, accountId, flags, {
8568
+ packetType: 'tool_call',
8569
+ world: { accountId, dataMode, branchName, workspace: toolWorkspace }
8570
+ }, { command: 'tool --name ' + String(toolName) });
8571
+
8174
8572
  const response = await loggedPost(api, cwd, '/cli/tool', {
8175
8573
  token: session.token,
8176
8574
  accountId,
@@ -8180,7 +8578,7 @@ async function toolCommand(flags) {
8180
8578
  // what the call may discover — has to survive that. The server validates it; it is never authorization.
8181
8579
  scopeRootAccountId: accountId,
8182
8580
  branchName,
8183
- workspace: resolveWorkspace(cwd, flags),
8581
+ workspace: toolWorkspace,
8184
8582
  dataMode,
8185
8583
  variantBranch,
8186
8584
  name: String(toolName),
@@ -8260,7 +8658,7 @@ async function toolCommand(flags) {
8260
8658
  if (jsonOutput) Object.assign(data, { finalStatus: finalStatus.data });
8261
8659
  }
8262
8660
 
8263
- await appendVerificationPacket(api, cwd, session, accountId, flags, {
8661
+ await appendVerificationPacket(api, cwd, session, accountId, verification.evidenceFlags, {
8264
8662
  type: 'tool_call',
8265
8663
  success: asyncMode && !waitForAsync ? null : (!toolFailed && (!asyncMode || process.exitCode !== 1)),
8266
8664
  pending: asyncMode && !waitForAsync,
@@ -8478,6 +8876,29 @@ async function corpusCommand(flags, subcommand) {
8478
8876
  return data;
8479
8877
  }
8480
8878
 
8879
+ // `verify token` / `verify tool` say "prove this for the envelope", so without an explicit --data-mode they run
8880
+ // in the envelope's lane when that lane is TEST. A plain `token`/`tool` follows the session, and a session parked
8881
+ // on prod otherwise refuses (or silently detaches) against the test envelope the agent just started. Never
8882
+ // adopts PROD: production proof keeps requiring an explicit --data-mode prod, which the refusal names.
8883
+ async function verifyWrapperFlags(api, cwd, session, accountId, flags, envelopeId) {
8884
+ const pinned = Object.assign({}, flags, { 'verify-envelope': envelopeId });
8885
+ if (hasExplicitDataModeFlag(flags)) return pinned;
8886
+ try {
8887
+ const result = await postVerificationCommand(api, cwd, session, accountId, 'compatibility', {
8888
+ envelopeId,
8889
+ probe: { packetType: 'manual_observation', world: {} }
8890
+ });
8891
+ const lane = result.comparisonWorld && result.comparisonWorld.dataMode;
8892
+ if (lane && normalizeDataMode(lane) === 'test' && resolveDataMode(flags, session) !== 'test') {
8893
+ console.error('Data lane: test (the lane envelope ' + envelopeId + ' proves; your session is on ' + resolveDataMode(flags, session) + ')');
8894
+ return Object.assign(pinned, { 'data-mode': 'test' });
8895
+ }
8896
+ } catch (_) {
8897
+ // The command's own preflight still answers; this only saves a turn.
8898
+ }
8899
+ return pinned;
8900
+ }
8901
+
8481
8902
  async function verifyCommand(flags, subcommand) {
8482
8903
  const cwd = process.cwd();
8483
8904
  ensureLocalState(cwd);
@@ -8486,11 +8907,26 @@ async function verifyCommand(flags, subcommand) {
8486
8907
  const sessionContext = resolveSessionContext(cwd, flags);
8487
8908
  const { session, accountId } = sessionContext;
8488
8909
  const baseUrl = flags['base-url'] || session.baseUrl || DEFAULT_BASE_URL;
8489
- const dataMode = resolveDataMode(flags, session);
8910
+ // `verify start` proves in the TEST lane unless told otherwise, for the reason `test run` does: production
8911
+ // proof needs explicit provenance. It used to inherit the session's lane, so a session parked on prod started
8912
+ // prod envelopes that every default `test run` then contradicted.
8913
+ const dataMode = sub === 'start' && !hasExplicitDataModeFlag(flags)
8914
+ ? DEFAULT_DATA_MODE
8915
+ : resolveDataMode(flags, session);
8490
8916
  const branchName = flags.branch || currentBranch(cwd);
8491
8917
  const workspace = resolveWorkspace(cwd, flags);
8492
8918
  const api = buildAxios(baseUrl, session.token);
8919
+ // Selection is by checkout world only (no data lane) - see verificationContextKey.
8493
8920
  const activeContext = activeVerificationContext(cwd, flags, session, accountId);
8921
+ const checkoutWorldLine = 'account=' + accountId + ', branch=' + branchName + ', workspace=' + (workspace || 'shared');
8922
+ // What a plain `test run` from here would record, for the "would it count" probe.
8923
+ const testRunWorld = {
8924
+ accountId,
8925
+ executionAccountId: Number(accountId),
8926
+ dataMode: hasExplicitDataModeFlag(flags) ? resolveDataMode(flags, null) : DEFAULT_DATA_MODE,
8927
+ branchName,
8928
+ workspace
8929
+ };
8494
8930
 
8495
8931
  if (sub === 'current') {
8496
8932
  const active = readActiveVerificationEnvelope(cwd, activeContext);
@@ -8498,18 +8934,30 @@ async function verifyCommand(flags, subcommand) {
8498
8934
  printResolvedBaseUrl(baseUrl);
8499
8935
  printLocalCommandContext(cwd, flags, { accountId, branchName, workspace, dataMode });
8500
8936
  printLocalStateWarnings(cwd, flags);
8501
- console.log('Context:', 'account=' + accountId + ', branch=' + branchName + ', workspace=' + (workspace || 'shared') + ', dataMode=' + dataMode);
8937
+ console.log('Checkout world:', checkoutWorldLine);
8502
8938
  if (!active) {
8503
- const legacy = readActiveVerificationEnvelope(cwd);
8504
- console.log('No active verification envelope for this context.');
8505
- if (legacy) {
8506
- console.log('Legacy active envelope ignored for this context:', legacy);
8939
+ console.log('No active verification envelope for this checkout world.');
8940
+ // Name the envelopes selected in SIBLING worlds (another workspace/branch of this checkout): the usual
8941
+ // cause is a workspace or branch switch, and "start a new one" is the wrong next step for that.
8942
+ const siblings = Object.values(readActiveVerificationContexts(cwd))
8943
+ .filter((entry) => entry && typeof entry === 'object' && entry.envelopeId && entry.context &&
8944
+ String(entry.context.accountId) === String(accountId))
8945
+ .sort((left, right) => String(right.updatedAt || '').localeCompare(String(left.updatedAt || '')))
8946
+ .filter((entry, index, all) => all.findIndex((other) => other.envelopeId === entry.envelopeId) === index)
8947
+ .slice(0, 3);
8948
+ siblings.forEach((entry) => {
8949
+ console.log(' selected in another world: ' + entry.envelopeId + ' (branch=' + (entry.context.branchName || '?') +
8950
+ ', workspace=' + (entry.context.workspace || 'shared') + ')');
8951
+ });
8952
+ if (siblings.length) {
8953
+ console.log(' Switch back to that workspace/branch, or `remits-cli verify use <id>` to select one here.');
8507
8954
  }
8508
8955
  return;
8509
8956
  }
8510
8957
  console.log('Active verification envelope:', active);
8511
8958
  const local = readLocalVerificationEnvelope(cwd, active);
8512
8959
  if (local && local.summary) console.log('Summary:', local.summary);
8960
+ await printVerificationRunProbe(api, cwd, session, accountId, active, testRunWorld);
8513
8961
  return;
8514
8962
  }
8515
8963
 
@@ -8520,7 +8968,8 @@ async function verifyCommand(flags, subcommand) {
8520
8968
  console.log('Active verification envelope:', envelopeId);
8521
8969
  printLocalCommandContext(cwd, flags, { accountId, branchName, workspace, dataMode });
8522
8970
  printLocalStateWarnings(cwd, flags);
8523
- console.log('Context:', 'account=' + accountId + ', branch=' + branchName + ', workspace=' + (workspace || 'shared') + ', dataMode=' + dataMode);
8971
+ console.log('Checkout world:', checkoutWorldLine);
8972
+ await printVerificationRunProbe(api, cwd, session, accountId, String(envelopeId), testRunWorld);
8524
8973
  return;
8525
8974
  }
8526
8975
 
@@ -8531,9 +8980,9 @@ async function verifyCommand(flags, subcommand) {
8531
8980
  console.log('All active verification envelope pointers cleared for this checkout.');
8532
8981
  } else {
8533
8982
  writeActiveVerificationEnvelope(cwd, null, activeContext);
8534
- console.log('Verification envelope cleared for this context.');
8983
+ console.log('Verification envelope cleared for this checkout world.');
8535
8984
  printLocalCommandContext(cwd, flags, { accountId, branchName, workspace, dataMode });
8536
- console.log('Context:', 'account=' + accountId + ', branch=' + branchName + ', workspace=' + (workspace || 'shared') + ', dataMode=' + dataMode);
8985
+ console.log('Checkout world:', checkoutWorldLine);
8537
8986
  }
8538
8987
  return;
8539
8988
  }
@@ -8586,7 +9035,13 @@ async function verifyCommand(flags, subcommand) {
8586
9035
  console.log('Verification envelope started:', envelope.envelopeId);
8587
9036
  printLocalCommandContext(cwd, flags, { accountId, branchName, workspace, dataMode });
8588
9037
  printLocalStateWarnings(cwd, flags);
8589
- console.log('Target:', 'account=' + accountId + ', branch=' + branchName + ', workspace=' + (workspace || 'shared') + ', dataMode=' + dataMode);
9038
+ console.log('Target:', checkoutWorldLine + ', dataMode=' + dataMode +
9039
+ (hasExplicitDataModeFlag(flags) ? '' : ' (verify start default; pass --data-mode prod to prove against production data)'));
9040
+ // Say NOW whether a plain `test run` from here will count - a prod lane or a manifest world elsewhere both
9041
+ // mean it will not - rather than after the first refusal. A PLAIN run: `--data-mode prod` on `verify start`
9042
+ // names the envelope's lane, not the run's. Silent when it attaches.
9043
+ await printVerificationRunProbe(api, cwd, session, accountId, envelope.envelopeId,
9044
+ Object.assign({}, testRunWorld, { dataMode: DEFAULT_DATA_MODE }), { onlyMismatch: true });
8590
9045
  printAcceptanceWarnings(envelope);
8591
9046
  printEnvelopeWarnings(envelope);
8592
9047
  console.log('Local mirror:', verificationPaths(cwd, envelope.envelopeId).base);
@@ -8637,11 +9092,18 @@ async function verifyCommand(flags, subcommand) {
8637
9092
  if (sub === 'show' || sub === 'read' || sub === 'get' || sub === 'packets') {
8638
9093
  const response = await postVerificationCommand(api, cwd, session, accountId, 'show', { envelopeId });
8639
9094
  if (response.envelope) writeLocalVerificationEnvelope(cwd, response.envelope);
9095
+ const envelope = response.envelope || {};
9096
+ const compact = flagEnabled(flags.summary) || flagEnabled(flags.compact);
8640
9097
  if (flagEnabled(flags.json)) {
8641
- console.log(JSON.stringify(response.envelope || response, null, 2));
8642
- return response.envelope || response;
9098
+ const payload = compact ? compactVerificationEnvelope(envelope, envelopeId) : (response.envelope || response);
9099
+ console.log(JSON.stringify(payload, null, 2));
9100
+ return payload;
9101
+ }
9102
+ if (compact) {
9103
+ const payload = compactVerificationEnvelope(envelope, envelopeId);
9104
+ printCompactVerificationEnvelope(payload);
9105
+ return payload;
8643
9106
  }
8644
- const envelope = response.envelope || {};
8645
9107
  printVerificationEnvelope(envelope, envelopeId);
8646
9108
  return envelope;
8647
9109
  }
@@ -8715,7 +9177,7 @@ async function verifyCommand(flags, subcommand) {
8715
9177
  return await testCommand(Object.assign({}, flags, { 'verify-envelope': envelopeId }));
8716
9178
  }
8717
9179
  if (sub === 'token') {
8718
- return await tokenCommand(Object.assign({}, flags, { 'verify-envelope': envelopeId }));
9180
+ return await tokenCommand(await verifyWrapperFlags(api, cwd, session, accountId, flags, envelopeId));
8719
9181
  }
8720
9182
  if (sub === 'stage') {
8721
9183
  return await pushComponentsCommand(Object.assign({}, flags, { 'verify-envelope': envelopeId }));
@@ -8724,7 +9186,7 @@ async function verifyCommand(flags, subcommand) {
8724
9186
  return await syncComponentsCommand(Object.assign({}, flags, { 'verify-envelope': envelopeId }));
8725
9187
  }
8726
9188
  if (sub === 'tool') {
8727
- return await toolCommand(Object.assign({}, flags, { 'verify-envelope': envelopeId }));
9189
+ return await toolCommand(await verifyWrapperFlags(api, cwd, session, accountId, flags, envelopeId));
8728
9190
  }
8729
9191
 
8730
9192
  if (sub === 'invalidate') {
@@ -8756,6 +9218,12 @@ async function verifyCommand(flags, subcommand) {
8756
9218
  if (sub === 'status' || sub === 'report') {
8757
9219
  let statusPacket = null;
8758
9220
  try {
9221
+ // Only attach the status observation when it is from the envelope's world; otherwise it is noise the
9222
+ // report would list as wrong-world evidence on every status check.
9223
+ const statusVerification = await verificationPreflight(api, cwd, session, accountId, { 'verify-envelope': envelopeId }, {
9224
+ packetType: 'component_status',
9225
+ world: { accountId, dataMode, branchName, workspace }
9226
+ }, { command: 'verify status', quiet: true, neverRefuse: true });
8759
9227
  const componentStatus = await loggedPost(api, cwd, '/cli/components', {
8760
9228
  token: session.token,
8761
9229
  accountId,
@@ -8774,22 +9242,16 @@ async function verifyCommand(flags, subcommand) {
8774
9242
  rawRefs: { command: 'components status' },
8775
9243
  componentStatus
8776
9244
  };
8777
- await appendVerificationPacket(api, cwd, session, accountId, { 'verify-envelope': envelopeId }, statusPacket, { quiet: true });
9245
+ await appendVerificationPacket(api, cwd, session, accountId, statusVerification.evidenceFlags, statusPacket, { quiet: true });
8778
9246
  } catch (err) {
8779
9247
  if (!flagEnabled(flags.json)) {
8780
9248
  console.error('Warning: could not attach component_status packet before rendering report:', describeError(err));
8781
9249
  }
8782
9250
  }
8783
- const response = await postVerificationCommand(api, cwd, session, accountId, sub, {
8784
- envelopeId,
8785
- currentWorld: buildCommandWorld(statusPacket && statusPacket.componentStatus, {
8786
- accountId,
8787
- dataMode,
8788
- branchName,
8789
- workspace,
8790
- host: normalizeBaseUrl(baseUrl)
8791
- })
8792
- });
9251
+ // No `currentWorld`: status and report judge the SAME world (manifest, else the envelope's start world), so
9252
+ // they cannot give two verdicts on one envelope. Sending the session's world made every --as-account packet
9253
+ // wrong-world under `status` while `report` verified it, and made the verdict depend on the session lane.
9254
+ const response = await postVerificationCommand(api, cwd, session, accountId, sub, { envelopeId });
8793
9255
  if (response.envelope) writeLocalVerificationEnvelope(cwd, response.envelope);
8794
9256
  if (response.report) {
8795
9257
  console.log(response.report);
@@ -13542,6 +14004,239 @@ function describeRunLocation(record) {
13542
14004
  return parts.join(' ');
13543
14005
  }
13544
14006
 
14007
+ async function activityInspectCommand(flags) {
14008
+ const cwd = process.cwd();
14009
+ ensureLocalState(cwd);
14010
+ const sessionContext = resolveSessionContext(cwd, flags);
14011
+ const { session, accountId } = sessionContext;
14012
+ const baseUrl = flags['base-url'] || session.baseUrl || DEFAULT_BASE_URL;
14013
+ const api = buildAxios(baseUrl, session.token, 30000);
14014
+ const response = await loggedPost(api, cwd, '/cli/activityInspect', {
14015
+ token: session.token,
14016
+ accountId,
14017
+ scope: flags.scope || 'self',
14018
+ dataMode: resolveDataMode(flags, session),
14019
+ userId: flags['user-id'] || flags.userId || flags['cli-user-id'],
14020
+ userEmail: flags['user-email'] || flags.email,
14021
+ limit: flags.limit || flags.max || 20,
14022
+ envelopeLimit: flags['envelope-limit'] || flags.envelopeLimit
14023
+ }).then((r) => r.data);
14024
+
14025
+ if (!response.success) throw new Error(response.message || 'Activity inspection failed');
14026
+ if (flagEnabled(flags.json)) {
14027
+ const payload = (flagEnabled(flags.summary) || flagEnabled(flags.compact))
14028
+ ? compactActivityInspect(response)
14029
+ : response;
14030
+ console.log(JSON.stringify(payload, null, 2));
14031
+ return payload;
14032
+ }
14033
+
14034
+ printSessionResolutionWarning(sessionContext);
14035
+ printResolvedBaseUrl(baseUrl);
14036
+ printActivityInspectSummary(response);
14037
+ return response;
14038
+ }
14039
+
14040
+ function smellSeverityRank(smell) {
14041
+ const severity = String((smell && smell.severity) || 'info').toLowerCase();
14042
+ if (severity === 'critical') return 0;
14043
+ if (severity === 'high') return 1;
14044
+ if (severity === 'medium') return 2;
14045
+ if (severity === 'low') return 3;
14046
+ return 4;
14047
+ }
14048
+
14049
+ function sortedActivitySmells(smells) {
14050
+ return (Array.isArray(smells) ? smells.slice() : []).sort((left, right) => {
14051
+ const severity = smellSeverityRank(left) - smellSeverityRank(right);
14052
+ if (severity) return severity;
14053
+ return String(right.updatedAt || right.at || '').localeCompare(String(left.updatedAt || left.at || ''));
14054
+ });
14055
+ }
14056
+
14057
+ function slimActivitySmell(smell) {
14058
+ const s = smell || {};
14059
+ return {
14060
+ severity: s.severity || 'info',
14061
+ type: s.type || 'signal',
14062
+ summary: s.summary || null,
14063
+ accountId: s.accountId || null,
14064
+ laneId: s.laneId || null,
14065
+ envelopeId: s.envelopeId || null,
14066
+ taskId: s.taskId || null,
14067
+ branchName: s.branchName || null,
14068
+ workspace: s.workspace || null
14069
+ };
14070
+ }
14071
+
14072
+ function compactActivityInspect(response) {
14073
+ const counts = response.counts || {};
14074
+ const smells = sortedActivitySmells(response.smells);
14075
+ const lanes = Array.isArray(response.lanes) ? response.lanes : [];
14076
+ const envelopes = Array.isArray(response.envelopes) ? response.envelopes : [];
14077
+ const testRuns = Array.isArray(response.testRuns) ? response.testRuns : [];
14078
+ const nextActions = smells
14079
+ .filter((smell) => smellSeverityRank(smell) <= 2)
14080
+ .slice(0, 5)
14081
+ .map((smell) => (smell.type || 'signal') + ': ' + (smell.summary || 'inspect this signal'));
14082
+
14083
+ return {
14084
+ success: response.success,
14085
+ accountId: response.accountId,
14086
+ accountName: response.accountName || null,
14087
+ scope: response.scope || 'self',
14088
+ dataMode: response.dataMode,
14089
+ accounts: response.accountIds || [],
14090
+ counts: {
14091
+ agents: counts.agents || 0,
14092
+ lanes: counts.lanes || lanes.length || 0,
14093
+ verificationEnvelopes: counts.verificationEnvelopes || envelopes.length || 0,
14094
+ testRuns: counts.testRuns || testRuns.length || 0,
14095
+ corpusResults: counts.corpusResults || 0,
14096
+ smells: smells.length
14097
+ },
14098
+ topSmells: smells.slice(0, 10).map(slimActivitySmell),
14099
+ currentLanes: lanes.slice(0, 10).map((lane) => ({
14100
+ laneId: lane.laneId || null,
14101
+ accountId: lane.accountId || null,
14102
+ branchName: lane.branchName || null,
14103
+ workspace: lane.workspace || null,
14104
+ stagedCount: lane.stagedCountAsOf || lane.stagedCount || 0,
14105
+ worksetCount: lane.worksetCountAsOf == null ? null : lane.worksetCountAsOf,
14106
+ userEmail: lane.userEmail || null,
14107
+ expiresInSeconds: lane.expiresInSeconds == null ? null : lane.expiresInSeconds
14108
+ })),
14109
+ recentEnvelopes: envelopes.slice(0, 8).map((env) => ({
14110
+ envelopeId: env.envelopeId || null,
14111
+ status: env.status || 'unknown',
14112
+ summary: env.summary || null,
14113
+ packetCount: env.packetCount || 0,
14114
+ currentFailureCount: env.currentFailureCount || 0,
14115
+ health: env.health || null,
14116
+ updatedAt: formatTime(env.updatedAtMs || env.lastPacketAtMs || env.createdAt)
14117
+ })),
14118
+ recentTestRuns: testRuns.slice(0, 8).map((run) => ({
14119
+ taskId: run.taskId || null,
14120
+ testName: run.testName || null,
14121
+ passed: run.passed || 0,
14122
+ total: run.total || 0,
14123
+ branchName: run.branchName || null,
14124
+ workspace: run.workspace || null,
14125
+ ai: corpusAiLabel(run),
14126
+ completedAt: formatTime(run.completedAt || run.startedAt)
14127
+ })),
14128
+ nextActions
14129
+ };
14130
+ }
14131
+
14132
+ function printActivityInspectSummary(response) {
14133
+ const subject = response.subjectUser
14134
+ ? ' for ' + (response.subjectUser.email || response.subjectUser.name || ('user ' + response.subjectUser.id))
14135
+ : '';
14136
+ console.log('Activity inspection' + subject + ' — ' + response.dataMode + ' lane');
14137
+ console.log('Scope: ' + (response.accountName || ('account ' + response.accountId)) +
14138
+ ' (' + response.accountId + '), ' + (response.scope || 'self') +
14139
+ ' · accounts ' + ((response.accountIds || []).join(', ') || response.accountId));
14140
+ const counts = response.counts || {};
14141
+ console.log('Counts: ' +
14142
+ (counts.agents || 0) + ' agent(s), ' +
14143
+ (counts.lanes || 0) + ' lane(s), ' +
14144
+ (counts.verificationEnvelopes || 0) + ' envelope(s), ' +
14145
+ (counts.testRuns || 0) + ' test run(s), ' +
14146
+ (counts.corpusResults || 0) + ' corpus result(s)');
14147
+
14148
+ const smells = sortedActivitySmells(response.smells);
14149
+ console.log('');
14150
+ if (!smells.length) {
14151
+ console.log('Smells: none detected in this slice.');
14152
+ } else {
14153
+ console.log('Top smells (' + smells.length + '):');
14154
+ smells.slice(0, 20).forEach((smell) => {
14155
+ console.log(' [' + (smell.severity || 'info') + '] ' + (smell.type || 'signal') + ': ' + smell.summary);
14156
+ const pivots = [
14157
+ smell.accountId ? 'account ' + smell.accountId : null,
14158
+ smell.laneId ? 'lane ' + smell.laneId : null,
14159
+ smell.envelopeId ? 'envelope ' + smell.envelopeId : null,
14160
+ smell.taskId ? 'task ' + smell.taskId : null,
14161
+ smell.testName || null,
14162
+ smell.branchName ? 'branch ' + smell.branchName : null,
14163
+ smell.workspace ? 'ws:' + smell.workspace : null
14164
+ ].filter(Boolean);
14165
+ if (pivots.length) console.log(' ' + pivots.join(' · '));
14166
+ });
14167
+ if (smells.length > 20) console.log(' ...' + (smells.length - 20) + ' more; rerun with --json');
14168
+ const actions = compactActivityInspect(response).nextActions;
14169
+ if (actions.length) {
14170
+ console.log('');
14171
+ console.log('Next actions:');
14172
+ actions.forEach((action) => console.log(' - ' + action));
14173
+ }
14174
+ }
14175
+
14176
+ const lanes = response.lanes || [];
14177
+ if (lanes.length) {
14178
+ console.log('');
14179
+ console.log('Current staging lanes:');
14180
+ lanes.slice(0, 12).forEach((lane) => {
14181
+ const ws = lane.workspace ? ' [ws:' + lane.workspace + ']' : ' [shared]';
14182
+ const workset = lane.worksetKnown && lane.worksetCountAsOf != null
14183
+ ? ' workset ' + lane.worksetCountAsOf
14184
+ : ' workset unknown';
14185
+ console.log(' - account ' + lane.accountId + ' ' + (lane.branchName || '?') + ws +
14186
+ ' overlay ' + (lane.stagedCountAsOf || 0) + workset +
14187
+ (lane.userEmail ? ' · ' + lane.userEmail : '') +
14188
+ (lane.expiresInSeconds != null ? ' · expires ~' + Math.round(lane.expiresInSeconds / 60) + 'm' : ''));
14189
+ });
14190
+ if (lanes.length > 12) console.log(' ...' + (lanes.length - 12) + ' more lane(s)');
14191
+ }
14192
+
14193
+ const envelopes = response.envelopes || [];
14194
+ if (envelopes.length) {
14195
+ console.log('');
14196
+ console.log('Recent verification envelopes:');
14197
+ envelopes.slice(0, 8).forEach((env) => {
14198
+ console.log(' - ' + (env.status || 'unknown') + ' · ' + (env.packetCount || 0) + ' packet(s) · ' +
14199
+ (env.summary || env.envelopeId) + ' · ' + formatTime(env.updatedAtMs || env.lastPacketAtMs || env.createdAt));
14200
+ if (env.currentFailureCount) console.log(' current failures: ' + env.currentFailureCount);
14201
+ });
14202
+ }
14203
+
14204
+ const runs = response.testRuns || [];
14205
+ if (runs.length) {
14206
+ console.log('');
14207
+ console.log('Recent test runs:');
14208
+ runs.slice(0, 8).forEach((run) => {
14209
+ console.log(' - ' + (run.testName || run.taskId) + ' · ' + (run.passed || 0) + '/' + (run.total || 0) +
14210
+ ' passed · ' + (run.branchName || '?') + (run.workspace ? ' [ws:' + run.workspace + ']' : '') +
14211
+ ' · ' + corpusAiLabel(run) + ' · ' + formatTime(run.completedAt || run.startedAt));
14212
+ });
14213
+ }
14214
+
14215
+ const corpus = response.corpusResults || [];
14216
+ if (corpus.length) {
14217
+ console.log('');
14218
+ console.log('Recent corpus measurements:');
14219
+ corpus.slice(0, 6).forEach((row) => {
14220
+ console.log(' - ' + (row.corpus || '?') + '/' + (row.caseKey || '?') + ' · ' +
14221
+ (row.outcome || 'unknown') + ' · ' + (row.testName || row.taskId) +
14222
+ ' · ' + formatTime(row.recordedAt));
14223
+ });
14224
+ }
14225
+
14226
+ const timeline = response.timeline || [];
14227
+ if (timeline.length) {
14228
+ console.log('');
14229
+ console.log('Timeline:');
14230
+ timeline.slice(0, 12).forEach((item) => {
14231
+ console.log(' - ' + formatTime(item.at) + ' · ' + item.type + ' · ' +
14232
+ (item.label || '') + (item.detail ? ' · ' + item.detail : ''));
14233
+ });
14234
+ }
14235
+ console.log('');
14236
+ console.log('Compact JSON: remits-cli activity inspect --summary --json');
14237
+ console.log('Full structured payload: remits-cli activity inspect --json');
14238
+ }
14239
+
13545
14240
  /**
13546
14241
  * The live work map: which repositories are being edited, by whom, and who else is around.
13547
14242
  *
@@ -13815,6 +14510,7 @@ async function agentCommand(flags, subcommand) {
13815
14510
  case 'work': return agentWorkCommand(flags);
13816
14511
  case 'list': case 'ls': return agentListCommand(flags);
13817
14512
  case 'map': case 'work-map': case 'workmap': return agentWorkMapCommand(flags);
14513
+ case 'inspect': case 'activity': return activityInspectCommand(flags);
13818
14514
  case 'repos': case 'repo': return agentReposCommand(flags);
13819
14515
  case 'release': case 'deregister': case 'stop': return agentReleaseCommand(flags);
13820
14516
  case 'heartbeat-daemon': return agentHeartbeatDaemonCommand(flags);
@@ -13867,6 +14563,8 @@ function printAgentHelp() {
13867
14563
  console.log('');
13868
14564
  console.log(' list [--account-id ID] [--json] Who else is available for an account.');
13869
14565
  console.log(' map [--account-ids 1,4] [--json] Who is EDITING which repository right now.');
14566
+ console.log(' inspect [--account-id ID] [--scope self|children] [--user-email E] [--json]');
14567
+ console.log(' Read-only workstream story + smell signals for another CLI user or agent fleet.');
13870
14568
  console.log(' One agent at a time may edit an account\'s repo (the edit lease); everyone else');
13871
14569
  console.log(' investigates read-only. Check this before starting work that changes files.');
13872
14570
  console.log(' repos [--rescan] [--json] WHERE each account is checked out on this machine —');
@@ -14505,17 +15203,18 @@ function printVerifyHelp() {
14505
15203
  console.log('Usage: remits-cli verify <subcommand>');
14506
15204
  console.log('');
14507
15205
  console.log('Envelope lifecycle:');
14508
- console.log(' remits-cli verify start --summary "..." [--manifest file.json] [--ticket ID]');
15206
+ console.log(' remits-cli verify start --summary "..." [--manifest file.json] [--ticket ID] [--data-mode test|prod]');
14509
15207
  console.log(' remits-cli verify list [--max 50] [--json]');
14510
15208
  console.log(' remits-cli verify use <envelopeId>');
14511
15209
  console.log(' remits-cli verify current');
14512
15210
  console.log(' remits-cli verify clear [--all]');
14513
- console.log(' remits-cli verify show [--envelope ID] [--json]');
15211
+ console.log(' remits-cli verify show [--envelope ID] [--summary|--compact] [--json]');
14514
15212
  console.log(' remits-cli verify status [--envelope ID]');
14515
15213
  console.log(' remits-cli verify report [--envelope ID]');
14516
15214
  console.log(' remits-cli verify abandon --envelope ID --reason "..."');
14517
15215
  console.log(' remits-cli verify supersede --envelope ID --superseded-by ID [--reason "..."]');
14518
- console.log(' Active envelopes are scoped by host, account, branch, workspace, and data mode.');
15216
+ console.log(' One active envelope per checkout world: host, account, git branch, workspace (not data lane).');
15217
+ console.log(' verify start proves in the test lane unless you pass --data-mode prod.');
14519
15218
  console.log('');
14520
15219
  console.log('Manifest and manual evidence:');
14521
15220
  console.log(' remits-cli verify manifest --file file.json [--envelope ID]');
@@ -14531,6 +15230,14 @@ function printVerifyHelp() {
14531
15230
  console.log(' remits-cli verify sync --safe');
14532
15231
  console.log(' remits-cli verify tool --name mcp_tool --input \'{...}\'');
14533
15232
  console.log('');
15233
+ console.log('World check (asked of the platform before stage, test run, token, tool, sync, and commit run):');
15234
+ console.log(' attach same world as the envelope requires - evidence attaches.');
15235
+ console.log(' refuse different world AND a requirement could use this evidence - nothing runs; the');
15236
+ console.log(' refusal names the flag that fixes it (e.g. --data-mode prod, --as-account 21).');
15237
+ console.log(' detach different world, nothing in the manifest could use it - runs, attaches nothing, says so.');
15238
+ console.log(' --allow-wrong-world-evidence attaches anyway as context (the report lists it wrong-world).');
15239
+ console.log(' verify token / verify tool run in the envelope\'s lane when it is test.');
15240
+ console.log('');
14534
15241
  console.log('Existing commands also accept --verify-envelope <id> and --no-verify-envelope.');
14535
15242
  }
14536
15243
 
@@ -14571,6 +15278,11 @@ async function main() {
14571
15278
  // Skip for lifecycle subcommands and help.
14572
15279
  const isLifecycleCmd = command === 'listen' || command === 'start' || command === 'stop' || command === 'status' || command === 'whoami' || command === 'doctor' || command === 'agent' || command === 'ticket';
14573
15280
  const isHelpCmd = !command || command === 'help' || command === '--help' || wantsHelp;
15281
+ if (!wantsJson && !isHelpCmd && command === 'components') {
15282
+ const commandName = 'components ' + (subcommand || 'command');
15283
+ const problem = platformRepoAccountCommandProblem(process.cwd(), args, commandName, null);
15284
+ if (problem) throw new Error(problem);
15285
+ }
14574
15286
  if (!wantsJson && !isHelpCmd && !isLifecycleCmd && !isListenerRunning()) {
14575
15287
  try {
14576
15288
  const dashboardUrl = await autoStartServiceIfNeeded();
@@ -14601,6 +15313,7 @@ async function main() {
14601
15313
  console.log(' remits-cli agent status [--state idle|working|paused] [--ticket ID] [--activity "..."]');
14602
15314
  console.log(' remits-cli agent list [--account-id ID] # who else is available');
14603
15315
  console.log(' remits-cli agent map [--account-ids 1,4] [--json] # who is EDITING what, where, right now');
15316
+ console.log(' remits-cli activity inspect [--account-id ID] [--scope self|children] [--user-email E] [--summary|--compact] [--json]');
14604
15317
  console.log(' remits-cli agent release # stop receiving tickets now');
14605
15318
  console.log(' remits-cli start [--foreground true] [--port 8787] # human dashboard in the browser');
14606
15319
  console.log(' remits-cli stop');
@@ -14651,7 +15364,7 @@ async function main() {
14651
15364
  console.log('A WORKSPACE is an optional second isolation axis for staging, alongside the git branch.');
14652
15365
  console.log('Staging is keyed by (account, cli user, branch, workspace) — and account and user are fixed');
14653
15366
  console.log('for a given repo, so without a workspace the branch is the ONLY axis. That is why two coding');
14654
- console.log('agents in two worktrees of the same branch overwrite each other: `components stage` uploads');
15367
+ console.log('agents in two checkouts without their own workspace overwrite each other: `components stage` uploads');
14655
15368
  console.log('the whole repo and replaces the lane wholesale.');
14656
15369
  console.log('');
14657
15370
  console.log('A workspace isolates staging WITHOUT changing anything else:');
@@ -14659,13 +15372,13 @@ async function main() {
14659
15372
  console.log(' - the run still resolves whatever committed ComponentVariant branch the account subscribes to');
14660
15373
  console.log(' - only which staged entries shadow them changes');
14661
15374
  console.log('');
14662
- console.log('Typical setup, one worktree per agent, all on the same branch:');
14663
- console.log(' git fetch origin');
15375
+ console.log('Typical setup, one clone per agent, all on the same branch:');
15376
+ console.log(' git clone <repo-url> ../ms-agent-a');
15377
+ console.log(' cd ../ms-agent-a');
14664
15378
  console.log(' git switch forked');
14665
15379
  console.log(' git pull --ff-only origin forked');
14666
15380
  console.log(' git rev-list --left-right --count HEAD...origin/forked # must print: 0 0');
14667
- console.log(' git worktree add --force ../ms-agent-a forked');
14668
- console.log(' cd ../ms-agent-a && remits-cli workspace use --auto');
15381
+ console.log(' remits-cli workspace use --auto');
14669
15382
  console.log(' remits-cli components status # branch world + staging lane + other lanes');
14670
15383
  console.log(' remits-cli components stage # isolated lane; nobody else sees it');
14671
15384
  console.log(' remits-cli test run --test 42 # resolves this lane over the forked variants');
@@ -14674,7 +15387,8 @@ async function main() {
14674
15387
  console.log('Precedence: --workspace > REMITS_WORKSPACE > .remits-cli/workspace > shared default lane');
14675
15388
  console.log('Use --no-workspace on a single command to target the shared lane without clearing the file.');
14676
15389
  console.log('');
14677
- console.log('`.remits-cli/workspace` is per-checkout and gitignored, so a worktree keeps its own lane.');
15390
+ console.log('`.remits-cli/workspace` is per-checkout and gitignored, so each clone keeps its own lane.');
15391
+ console.log('Avoid same-branch worktrees for concurrent agents: they share the branch ref, so one pull moves HEAD under the others, and `components commit` refuses there.');
14678
15392
  process.exit(0);
14679
15393
  }
14680
15394
 
@@ -14823,6 +15537,22 @@ async function main() {
14823
15537
  return;
14824
15538
  }
14825
15539
 
15540
+ if (command === 'activity') {
15541
+ if (wantsHelp || !subcommand) {
15542
+ console.log('Usage: remits-cli activity inspect [--account-id ID] [--scope self|children] [--user-id ID|--user-email EMAIL] [--limit N] [--summary|--compact] [--json]');
15543
+ console.log('');
15544
+ console.log('Read-only workstream inspection for agent/operator review. Joins current staging lanes,');
15545
+ console.log('verification envelopes, durable test runs, corpus measurements, live agent presence,');
15546
+ console.log('and heuristic smell signals without changing any lane or checkout.');
15547
+ return;
15548
+ }
15549
+ if (subcommand === 'inspect') {
15550
+ await activityInspectCommand(args);
15551
+ return;
15552
+ }
15553
+ throw new Error('Unknown activity subcommand: ' + subcommand);
15554
+ }
15555
+
14826
15556
  if (command === 'ticket') {
14827
15557
  if (wantsHelp || !subcommand) {
14828
15558
  printTicketHelp();