@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/README.md +2 -0
- package/index.js +828 -98
- package/package.json +1 -1
- package/skills/remits-cli/references/branch-variants.md +10 -4
- package/skills/remits-cli/references/command-reference.md +27 -1
- package/skills/remits-cli/references/development-loop.md +19 -0
- package/skills/remits-cli/references/tool-reference.md +4 -3
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
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
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
|
-
|
|
1420
|
-
|
|
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
|
|
1456
|
-
|
|
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
|
|
1505
|
-
if (explicit
|
|
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
|
-
|
|
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
|
|
1686
|
-
//
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
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
|
-
//
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
'.
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
|
7295
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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:
|
|
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,
|
|
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
|
-
|
|
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('
|
|
8937
|
+
console.log('Checkout world:', checkoutWorldLine);
|
|
8502
8938
|
if (!active) {
|
|
8503
|
-
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
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('
|
|
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
|
|
8983
|
+
console.log('Verification envelope cleared for this checkout world.');
|
|
8535
8984
|
printLocalCommandContext(cwd, flags, { accountId, branchName, workspace, dataMode });
|
|
8536
|
-
console.log('
|
|
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:',
|
|
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
|
-
|
|
8642
|
-
|
|
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(
|
|
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(
|
|
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,
|
|
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
|
-
|
|
8784
|
-
|
|
8785
|
-
|
|
8786
|
-
|
|
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('
|
|
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
|
|
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
|
|
14663
|
-
console.log(' git
|
|
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('
|
|
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
|
|
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();
|