@remits/remits-cli 0.1.138 → 0.1.141

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 CHANGED
@@ -33,6 +33,7 @@ git add -A
33
33
  git commit -m "sync passing changes"
34
34
  git push
35
35
  remits-cli components sync --safe # variant branch: gated, dry-runs first, refuses surprises
36
+ remits-cli components sync doctor # read-only sync/promotion/staging diagnosis
36
37
  remits-cli components sync
37
38
  remits-cli components sync --branch feature_branch --dry-run
38
39
  remits-cli components sync --branch feature_branch --dry-run --summary
@@ -84,7 +85,7 @@ remits-cli install --skills --overwrite true
84
85
  - `components sync` performs a server-side sync from the git remote into the Remits platform for the selected branch. It does not run local git commands. After a successful non-dry-run sync, the platform clears the branch/user staging scope so staged aliases cannot keep shadowing the newly synced DB rows.
85
86
  - On trunk, `components sync` performs the full repo-to-DB reconcile. On a non-trunk branch, it writes `ComponentVariant` overlays only and may refresh branch-local `account-info.json`, `account-hierarchy.json`, and `account-configurations.json` for the subscribing account that initiated the sync. `--dry-run` is accepted only for non-trunk variant syncs and reports overrides/additions/tombstones without writing variants, caching the sync SHA, updating metadata, or clearing staging. Add `--summary` to dry-run output when you only need counts, removals/tombstones, errors, skipped items, and warnings. `--force-tombstones` is accepted only for non-trunk variant syncs and should be used only when missing trunk component files are intentional tombstone overrides.
86
87
  - `components commit` is a convenience wrapper that performs local git commit/push, then `components sync`, then local `git fetch`/`git pull --ff-only`.
87
- - `components sync` returns the post-sync branch SHA produced by the platform. `components commit` verifies that `origin/<branch>` and local `HEAD` both match that exact SHA after the final pull.
88
+ - `components sync` returns the post-sync branch SHA produced by the platform. `components sync doctor` is read-only and assembles local HEAD, origin HEAD, platform last-sync SHA, staging shadow risk, promotion freshness, repository mismatch, and the next safe action when sync state is confusing. `components commit` verifies that `origin/<branch>` and local `HEAD` both match the exact synced SHA after the final pull.
88
89
  - `components push` is deprecated and currently behaves the same as `components stage`.
89
90
  - `accountId` resolution for CLI commands: explicit `--account-id` flag wins, then the current repo's `account-info.json` (`resolution.accountId`, then the short-lived `repoContext.accountInfoAccountId`, then the legacy top-level id — legacy files are rooted at the hierarchy ROOT, so their top-level `id` may be an ancestor), then the active session.
90
91
  - For `remits-cli tool`, read/discovery tools treat that resolved account as the **scope root**. You do not need the owning child account id for an exact document/record lookup: pass `--scope children`, or put `scope:"children"` in `--input`, and use the returned `resolvedTargetAccountId` for follow-up writes/runs. Use `--target-account-id` when you already know the exact owner, `--account-ids` for an explicit bounded owner list, and `--anchor-account-id` only to disambiguate multi-parent paths. Mutating tools still require an exact target.
@@ -100,8 +101,8 @@ remits-cli install --skills --overwrite true
100
101
  - Branch defaults to the current local git branch.
101
102
  - Data mode defaults to `test`. For `remits-cli test run`, that default is now enforced even if the most-recent authenticated session for the account is `prod`; a production test run therefore requires an explicit `--data-mode prod` on the command line. Use `remits-cli data-mode set prod` only for production investigation.
102
103
  - If the same account is authenticated against more than one host and you omit `--base-url`, the CLI auto-resolves the best matching session and now prints the resolved host. Pass `--base-url` explicitly whenever the target host matters.
103
- - `--names` is `|`-delimited and may be repeated. A comma still splits a single value for compatibility,
104
- so a case name containing a comma should be passed with `|` or by repeating `--names`.
104
+ - `--names` is `|`-delimited and may be repeated. A comma is part of the selector, so comma-bearing case
105
+ names can be passed literally.
105
106
  - Nested help is available before required-argument validation, including `remits-cli test run --help`, `remits-cli components sync --help`, and `remits-cli tool --help`.
106
107
  - `components sync --safe` is the recommended agent path on a variant branch. It expands to `--summary --changed-only --fail-on-errors --fail-on-removed`, resolves `--changed-since` from the branch's merge base with trunk when you did not name one (local refs only; it never runs an implicit `git fetch`), and prints the planned writes before mutating unless `--yes` is passed. On trunk there is no plan to gate, so it states what a trunk reconcile does and requires `--yes`.
107
108
  - `components sync` has fail-closed safety gates for unattended/agent use. Each exits non-zero instead of printing a wall of JSON: `--changed-only` (fail unless every planned write is a component this checkout edited), `--names-only` (print only `BUCKET type:id name` lines), `--fail-on-removed`, `--fail-on-errors`, and `--expected-removed <type:id>` (repeatable or comma-delimited; implies `--fail-on-removed`, so any removal you did not name fails). `--changed-only` also fails closed when the checkout is not a git working tree, because "git could not answer" must never be read as "nothing changed".
package/index.js CHANGED
@@ -11,11 +11,11 @@
11
11
  - L4107 Skill Delivery And TOC Resolution
12
12
  - L4417 Auth And Component Staging
13
13
  - L5032 Component Summaries, Status, And Sync Gates
14
- - L7104 Branches, Promotion, Commit, And Test Runs
15
- - L9193 Tokens, Tools, Verification, And Config
16
- - L10620 Service Dashboard And WebSocket Listener
17
- - L12743 Agent And Ticket Workflows
18
- - L16338 Help, Auto Update, And Command Dispatch
14
+ - L7332 Branches, Promotion, Commit, And Test Runs
15
+ - L9421 Tokens, Tools, Verification, And Config
16
+ - L10848 Service Dashboard And WebSocket Listener
17
+ - L12971 Agent And Ticket Workflows
18
+ - L16566 Help, Auto Update, And Command Dispatch
19
19
  */
20
20
 
21
21
  /*
@@ -5994,6 +5994,234 @@ async function clearComponentsCommand(flags) {
5994
5994
  return response;
5995
5995
  }
5996
5996
 
5997
+ async function syncDoctorComponentsCommand(rawFlags) {
5998
+ const cwd = process.cwd();
5999
+ const flags = Object.assign({}, rawFlags);
6000
+ ensureLocalState(cwd);
6001
+ const sessionContext = resolveSessionContext(cwd, flags);
6002
+ requireAccountRepoContext(cwd, flags, 'components sync doctor', sessionContext);
6003
+ const { session, accountId } = sessionContext;
6004
+ const baseUrl = flags['base-url'] || session.baseUrl || DEFAULT_BASE_URL;
6005
+ const branchName = flags.branch || currentBranch(cwd);
6006
+ const workspace = resolveWorkspace(cwd, flags);
6007
+ const dataMode = resolveDataMode(flags, session);
6008
+ const api = buildAxios(baseUrl, session.token);
6009
+
6010
+ const statusResponse = await loggedPost(api, cwd, '/cli/components', {
6011
+ token: session.token,
6012
+ accountId,
6013
+ branchName,
6014
+ workspace,
6015
+ dataMode,
6016
+ mode: 'status'
6017
+ }).then((r) => r.data);
6018
+
6019
+ if (!statusResponse.success) {
6020
+ throw new Error(statusResponse.message || 'Staging status failed');
6021
+ }
6022
+
6023
+ statusResponse.freshness = stagingFreshness({
6024
+ head: safeGitValue(cwd, 'git rev-parse HEAD'),
6025
+ originHead: safeGitValue(cwd, 'git rev-parse ' + shellQuote('origin/' + branchName)),
6026
+ lastSyncedSha: statusResponse.branchContext && statusResponse.branchContext.lastSyncedSha,
6027
+ entries: statusResponse.entries,
6028
+ laneSummary: statusResponse.laneSummary,
6029
+ isAncestor: (a, b) => gitIsAncestor(cwd, a, b)
6030
+ });
6031
+ statusResponse.repositoryCheck = repositoryCheck(cwd, statusResponse.branchContext);
6032
+
6033
+ let promotionResponse = null;
6034
+ try {
6035
+ promotionResponse = await loggedPost(api, cwd, '/cli/branches', {
6036
+ token: session.token,
6037
+ accountId,
6038
+ mode: 'promotion',
6039
+ branchName
6040
+ }, { skipSessionLog: true }).then((r) => r.data);
6041
+ } catch (err) {
6042
+ const data = err && err.response && err.response.data;
6043
+ promotionResponse = {
6044
+ success: false,
6045
+ message: data && data.message ? data.message : describeError(err)
6046
+ };
6047
+ }
6048
+
6049
+ const report = buildSyncDoctorReport({
6050
+ accountId,
6051
+ branchName,
6052
+ workspace,
6053
+ dataMode: statusResponse.dataMode || dataMode,
6054
+ baseUrl: normalizeBaseUrl(baseUrl),
6055
+ local: {
6056
+ head: safeGitValue(cwd, 'git rev-parse HEAD'),
6057
+ originHead: safeGitValue(cwd, 'git rev-parse ' + shellQuote('origin/' + branchName)),
6058
+ upstream: safeGitValue(cwd, 'git rev-parse --abbrev-ref --symbolic-full-name @{u}'),
6059
+ aheadBehind: safeGitValue(cwd, 'git rev-list --left-right --count HEAD...origin/' + shellQuote(branchName))
6060
+ },
6061
+ status: statusResponse,
6062
+ promotion: promotionResponse
6063
+ });
6064
+
6065
+ if (flagEnabled(flags.json)) {
6066
+ console.log(JSON.stringify(report, null, 2));
6067
+ return report;
6068
+ }
6069
+
6070
+ printSessionResolutionWarning(sessionContext);
6071
+ printResolvedBaseUrl(baseUrl);
6072
+ printSyncDoctorReport(report);
6073
+ return report;
6074
+ }
6075
+
6076
+ function selectedPromotionEntry(promotion, branchName) {
6077
+ if (!promotion || promotion.success === false) return null;
6078
+ const entries = Array.isArray(promotion.branches) ? promotion.branches : [promotion];
6079
+ return entries.find((entry) => String(entry && entry.branch || '') === String(branchName)) || entries[0] || null;
6080
+ }
6081
+
6082
+ function syncDoctorStagedCount(status) {
6083
+ const laneSummary = status && status.laneSummary || {};
6084
+ const candidates = [laneSummary.stagedCount, laneSummary.count, laneSummary.total, status && status.stagedCount];
6085
+ for (const value of candidates) {
6086
+ const number = Number(value);
6087
+ if (Number.isFinite(number)) return number;
6088
+ }
6089
+ return Array.isArray(status && status.entries) ? status.entries.length : 0;
6090
+ }
6091
+
6092
+ function buildSyncDoctorReport(input) {
6093
+ const status = input.status || {};
6094
+ const promotion = input.promotion || null;
6095
+ const entry = selectedPromotionEntry(promotion, input.branchName);
6096
+ const platform = entry && entry.platform || {};
6097
+ const git = entry && entry.git || {};
6098
+ const branchContext = status.branchContext || {};
6099
+ const stagedCount = syncDoctorStagedCount(status);
6100
+ const report = {
6101
+ success: status.success !== false,
6102
+ accountId: input.accountId,
6103
+ branchName: input.branchName,
6104
+ workspace: input.workspace || null,
6105
+ dataMode: input.dataMode,
6106
+ host: input.baseUrl,
6107
+ local: input.local || {},
6108
+ world: status.variantWorld || null,
6109
+ branchContext,
6110
+ repositoryCheck: status.repositoryCheck || null,
6111
+ staging: {
6112
+ laneId: status.laneSummary && status.laneSummary.laneId || status.stagingLane || null,
6113
+ stagedCount,
6114
+ contentHash: status.laneSummary && status.laneSummary.contentHash || null,
6115
+ freshness: status.freshness || null
6116
+ },
6117
+ platform: {
6118
+ lastSyncedSha: branchContext.lastSyncedSha || null,
6119
+ syncRepository: branchContext.repository || null,
6120
+ mode: branchContextIsTrunk(branchContext) ? 'trunk' : 'variant'
6121
+ },
6122
+ promotion: entry ? {
6123
+ available: true,
6124
+ branch: entry.branch,
6125
+ phase: entry.phase || null,
6126
+ summary: entry.summary || null,
6127
+ remoteBranchHeadSha: git.branchHeadSha || null,
6128
+ remoteTrunkHeadSha: git.trunkHeadSha || null,
6129
+ overlaysComputedFrom: platform.syncedSha || null,
6130
+ overlayFreshness: platform.freshness || null,
6131
+ overridden: platform.overridden,
6132
+ branchOnly: platform.added,
6133
+ tombstoned: platform.removed,
6134
+ drifted: platform.drifted,
6135
+ blockers: entry.blockers || [],
6136
+ nextSteps: entry.nextSteps || []
6137
+ } : {
6138
+ available: false,
6139
+ message: promotion && promotion.message || 'promotion status unavailable'
6140
+ }
6141
+ };
6142
+ report.recommendations = syncDoctorRecommendations(report);
6143
+ return report;
6144
+ }
6145
+
6146
+ function syncDoctorRecommendations(report) {
6147
+ const recs = [];
6148
+ const repo = report.repositoryCheck;
6149
+ if (repo && repo.matches === false) {
6150
+ recs.push('Run sync from the checkout whose origin matches the platform repository: ' + repo.platformRepository + '.');
6151
+ }
6152
+ const freshness = report.staging && report.staging.freshness || {};
6153
+ if (freshness.landedSinceHead === true) {
6154
+ recs.push('Pull the platform-synced commit before trusting this lane: git fetch origin && git pull --ff-only, then re-stage.');
6155
+ }
6156
+ if (freshness.entriesBehindLastSync > 0) {
6157
+ recs.push('Re-stage from the current checkout with `remits-cli components stage --workset`; the lane shadows rows landed after it was staged.');
6158
+ }
6159
+ if ((report.staging && report.staging.stagedCount || 0) > 0) {
6160
+ recs.push('Committed-variant verification will be shadowed by this staging lane; clear it or use an unused --branch with --variant-branch before proving committed source.');
6161
+ }
6162
+ const promo = report.promotion || {};
6163
+ if (promo.available && Array.isArray(promo.blockers) && promo.blockers.length) {
6164
+ recs.push('Do not sync yet; `components promotion` reports blockers. Follow its next steps before mutating.');
6165
+ } else if (promo.available && promo.overlayFreshness && promo.overlayFreshness !== 'current') {
6166
+ recs.push('Stored overlays are stale relative to the branch/trunk pair. Re-read promotion, merge as instructed, then run one gated sync.');
6167
+ }
6168
+ if (report.platform && report.platform.mode === 'variant') {
6169
+ recs.push('Agent landing path: push the branch, run `remits-cli components sync --safe --yes` once, then `git pull --ff-only`.');
6170
+ } else {
6171
+ recs.push('Trunk sync has no dry-run plan. Use `remits-cli components sync --safe --yes` only when you intend the authoritative trunk reconcile.');
6172
+ }
6173
+ recs.push('If a sync times out client-side, treat the result as unknown: observe with `components sync doctor` or `components promotion`; do not put `components sync` in a polling loop.');
6174
+ return Array.from(new Set(recs));
6175
+ }
6176
+
6177
+ function printSyncDoctorReport(report) {
6178
+ console.log('Sync doctor');
6179
+ console.log('Account:', report.accountId);
6180
+ console.log('Data mode:', report.dataMode);
6181
+ console.log('Git branch:', report.branchName);
6182
+ console.log('Workspace:', report.workspace || 'shared default lane');
6183
+ console.log('Mode:', report.platform.mode);
6184
+ console.log('');
6185
+ console.log('Local git:');
6186
+ console.log(' HEAD: ' + short(report.local.head));
6187
+ console.log(' origin HEAD: ' + short(report.local.originHead));
6188
+ if (report.local.upstream) console.log(' upstream: ' + report.local.upstream);
6189
+ if (report.local.aheadBehind) console.log(' HEAD...origin/' + report.branchName + ': ' + report.local.aheadBehind.trim() + ' (left=local, right=remote)');
6190
+ console.log('');
6191
+ console.log('Platform sync:');
6192
+ console.log(' repository: ' + (report.platform.syncRepository || 'unknown'));
6193
+ console.log(' last synced SHA: ' + short(report.platform.lastSyncedSha));
6194
+ if (report.repositoryCheck && report.repositoryCheck.matches === false) {
6195
+ console.log(' REPOSITORY MISMATCH: checkout origin is ' + report.repositoryCheck.checkoutOrigin);
6196
+ }
6197
+ console.log('');
6198
+ console.log('Staging lane:');
6199
+ console.log(' staged entries: ' + report.staging.stagedCount);
6200
+ if (report.staging.contentHash) console.log(' content hash: ' + report.staging.contentHash);
6201
+ printStagingFreshness(report.staging.freshness);
6202
+ console.log('');
6203
+ console.log('Promotion / variant status:');
6204
+ if (report.promotion.available) {
6205
+ console.log(' phase: ' + (report.promotion.phase || 'unknown'));
6206
+ console.log(' remote branch HEAD: ' + short(report.promotion.remoteBranchHeadSha));
6207
+ console.log(' overlays computed from:' + ' ' + short(report.promotion.overlaysComputedFrom) +
6208
+ (report.promotion.overlayFreshness ? ' [' + report.promotion.overlayFreshness + ']' : ''));
6209
+ console.log(' overlays: overridden ' + (report.promotion.overridden ?? 'unknown') +
6210
+ ', branch-only ' + (report.promotion.branchOnly ?? 'unknown') +
6211
+ ', tombstoned ' + (report.promotion.tombstoned ?? 'unknown') +
6212
+ ', drifted ' + (report.promotion.drifted ?? 'unknown'));
6213
+ if (report.promotion.blockers && report.promotion.blockers.length) {
6214
+ console.log(' blockers: ' + report.promotion.blockers.length);
6215
+ report.promotion.blockers.slice(0, 8).forEach((b) => console.log(' - ' + b.code + ': ' + b.message));
6216
+ }
6217
+ } else {
6218
+ console.log(' unavailable: ' + report.promotion.message);
6219
+ }
6220
+ console.log('');
6221
+ console.log('Recommended next action:');
6222
+ report.recommendations.forEach((line) => console.log(' - ' + line));
6223
+ }
6224
+
5997
6225
  async function syncComponentsCommand(rawFlags) {
5998
6226
  const cwd = process.cwd();
5999
6227
  // `--safe` is a NAME for the gate combination that should be the default agent path, not a new gate.
@@ -16537,11 +16765,14 @@ function printComponentsHelp(subcommand) {
16537
16765
  }
16538
16766
  if (subcommand === 'sync') {
16539
16767
  console.log('Usage: remits-cli components sync [--base-url URL] [--account-id ID] [--branch BRANCH] [--data-mode test|prod] [--force-tombstones] [--dry-run] [--create-variant-branch] [--summary] [--json]');
16768
+ console.log(' remits-cli components sync doctor [--json]');
16540
16769
  console.log('');
16541
16770
  console.log('On trunk this reconciles the pushed repository into live component rows.');
16542
16771
  console.log('On a variant branch this writes ComponentVariant overlays only.');
16543
16772
  console.log('--dry-run is only supported on variant branches and writes nothing.');
16544
16773
  console.log('--summary prints compact counts, removals/tombstones, errors, skipped items, and warnings.');
16774
+ console.log('`sync doctor` is read-only. It assembles local HEAD, origin HEAD, platform last sync,');
16775
+ console.log('staging shadow risk, promotion freshness, repository mismatch, and the next safe action.');
16545
16776
  console.log('');
16546
16777
  console.log('Agent safety gates (each exits non-zero instead of printing a wall of JSON):');
16547
16778
  console.log(' --safe THE RECOMMENDED PATH on a variant branch. Expands to');
@@ -17084,6 +17315,11 @@ async function main() {
17084
17315
  return;
17085
17316
  }
17086
17317
 
17318
+ if (command === 'components' && subcommand === 'sync' && String(args._ && args._[2] || '').toLowerCase() === 'doctor') {
17319
+ await syncDoctorComponentsCommand(args);
17320
+ return;
17321
+ }
17322
+
17087
17323
  if (command === 'components' && subcommand === 'sync') {
17088
17324
  await syncComponentsCommand(args);
17089
17325
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remits/remits-cli",
3
- "version": "0.1.138",
3
+ "version": "0.1.141",
4
4
  "description": "Local CLI for auth, component sync, and live test execution against Remits",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -92,6 +92,12 @@ reference named after it.
92
92
  "branch" or "verified"; say what was actually proven: Test pass, browser journey, corpus measurement,
93
93
  sync mutation, or ticket lifecycle state. (`component-resolution.md`, `branch-variants.md`,
94
94
  `development-loop.md`, `support-tickets.md`)
95
+ - **Embeddable verification is browser verification.** For page, widget, form, upload, or other
96
+ browser-facing changes, mint a URL with `remits-cli token` and drive it with `playwright-cli`.
97
+ Prefer this over `curl`: `curl` can prove an HTTP response or inspect an action payload, but it does
98
+ not run JavaScript, Alpine, CSS/assets, redirects, cookies/session handling, uploads, iframes,
99
+ shadow-DOM placement, or websocket behavior. (`development-loop.md`,
100
+ `docs/front-stage/components/embeddable-components.md`, `docs/front-stage/features/web-browser-automation.md`)
95
101
  - **Land only from trunk or a real variant branch.** A per-agent/feature branch (`components status` says
96
102
  `FEATURE BRANCH … [subscription-fallback]`) is safe to stage and run from — it resolves the same world
97
103
  as its target — but `components commit`/`sync` refuse it. Merge into the branch it resolves and land
@@ -107,6 +113,11 @@ reference named after it.
107
113
  updating, renaming, and **hard-deleting** rows. Prefer the observable
108
114
  `git commit → git push → components sync → git pull`. On trunk it refuses before any git write unless
109
115
  you pass `--yes`. (`component-integrity.md`)
116
+ - **Do not hand-roll sync watchers.** A sync is a write; an observation loop must never start a fresh
117
+ `components sync`. Use `remits-cli components sync doctor` and `components promotion` to read sync /
118
+ overlay state. If a sync times out client-side, the result is unknown, not failed; observe before any
119
+ retry. Do not use `setsid`, `nohup`, `disown`, `tmux`, or shell-specific detach tricks to make sync
120
+ lifecycle work. (`development-loop.md`, `branch-variants.md`)
110
121
  - **After anyone lands, ask whether your lane is stale: `remits-cli components status`.** A landing clears
111
122
  only the lander's lane; yours keeps shadowing their new rows with content staged from the old base.
112
123
  `LANDED SINCE YOUR BASE` / `STALE OVERLAY` mean pull, re-stage, then verify — a pass against a stale
@@ -169,6 +180,12 @@ reference named after it.
169
180
  component (preferred, because it becomes regression protection) or a browser flow through
170
181
  `remits-cli token`. "Just do it" and "that's fine, commit it" are not evidence. If you genuinely
171
182
  cannot verify, say what you would need and ask. (`development-loop.md`)
183
+ - **Match the proof to the claim.** A mocked Test is a seam proof; a sync packet is durable source
184
+ movement; a token/browser run is journey proof; a corpus consistency report is repeatability proof. If
185
+ the request says "same file twice", "repeatability", "consistency", "same answer", or "model variance",
186
+ start with a corpus/evaluation or other repeated live measurement under an explicit AI budget; mocked
187
+ seam suites are guardrails, not acceptance. (`development-loop.md`, `component-resolution.md`,
188
+ `docs/front-stage/components/test-components.md`)
172
189
  - **`remits-cli evidence` answers "what have I actually run, and in which world?"** Every stage, test,
173
190
  token, tool and sync appends one world-stamped line automatically, per actor. Nothing to start,
174
191
  nothing to satisfy. Read it before you re-run something, and quote it when you report what you
@@ -12,6 +12,7 @@
12
12
  - [Which world does your working tree resolve? (read this before you run anything)](#which-world-does-your-working-tree-resolve-read-this-before-you-run-anything)
13
13
  - [Two levers, two different questions](#two-levers-two-different-questions)
14
14
  - [The SDLC is identical on a variant branch](#the-sdlc-is-identical-on-a-variant-branch)
15
+ - [Long-running variant sync](#long-running-variant-sync)
15
16
  - [Where am I in the promotion loop?](#where-am-i-in-the-promotion-loop)
16
17
  - [Subscribing, unsubscribing, retiring](#subscribing-unsubscribing-retiring)
17
18
  - [Danger profile on a variant branch (different, not absent)](#danger-profile-on-a-variant-branch-different-not-absent)
@@ -237,7 +238,7 @@ remits-cli test run --test "Invoice Tests" # the feature_bran
237
238
  remits-cli test run --test "Invoice Tests" --as-account 101 # ...as the real subscriber
238
239
  git add -A && git commit -m "..." && git push origin feature_branch
239
240
  remits-cli components sync --dry-run # inspect overrides/additions/tombstones without writes
240
- remits-cli components sync # writes ComponentVariant overlays ONLY
241
+ remits-cli components sync --safe --yes # agent/noninteractive: writes ComponentVariant overlays ONLY
241
242
  ```
242
243
 
243
244
  `components stage` validates changed runtime-compiled source through the active branch/workspace lane,
@@ -252,6 +253,41 @@ for you, and merging a branch promotes its **deletions** as hard deletes. Do not
252
253
  `remits-cli components sync --summary --timeout-ms 300000`; the platform may need longer than the default
253
254
  60 seconds to create rows, push rename/meta commits, and regenerate account metadata.
254
255
 
256
+ ### Long-running variant sync
257
+
258
+ A variant sync writes durable `ComponentVariant` overlays only. The proof that subscribers can resolve the
259
+ new committed branch source is the overlay set's computed SHA, not the fact that your local process once
260
+ started a sync.
261
+
262
+ Use this shape for agent landings:
263
+
264
+ ```bash
265
+ git push origin <branch>
266
+ remits-cli components sync --safe --yes
267
+ git pull --ff-only origin <branch>
268
+ remits-cli components sync doctor
269
+ remits-cli components promotion --branch <branch> --no-fail
270
+ ```
271
+
272
+ `--safe --yes` skips the interactive prompt after the safe dry-run gates pass; it does not skip the gates.
273
+ If the client times out, the result is **unknown**. Do not immediately re-run `components sync`, and never
274
+ write a polling loop that invokes it. Observe with `components sync doctor` and
275
+ `components promotion --branch <branch> --no-fail`; a read loop may inspect, but only one command should
276
+ start the write.
277
+
278
+ Read the promotion report precisely:
279
+
280
+ - `branch HEAD` in the git section says what the remote branch currently points at.
281
+ - `overlays computed from` in the platform section says what subscribers resolve right now.
282
+ - A cached status or a local process exit is not proof that overlays advanced.
283
+ - Immediately after a sync, metadata refresh and compile caches can lag by a moment; committed-variant
284
+ verification still has to clear or avoid staged overlays first.
285
+
286
+ Host and account provenance are part of this proof. A subscriber can be subscribed to a branch that
287
+ currently stores zero overlays, which means the selected branch resolves trunk behavior. Before comparing
288
+ uploads or browser runs, read the token/test/tool world: host, execution account, component branch,
289
+ `variantBranchSource`, data lane, and resolved database.
290
+
255
291
  ### Where am I in the promotion loop?
256
292
 
257
293
  ```bash
@@ -246,6 +246,7 @@ remits-cli components lanes [--json] # every indexed sta
246
246
  remits-cli components entries --lane-id <id> [--json|--verbose] # authoritative staged files for one lane
247
247
  remits-cli components clear [--branch <name>] [--component-type <type>] [--component-id <id>] [--all] [--json|--verbose] # id alone scopes to one component when unambiguous (ids are type-local; add --component-type if the same id is staged in multiple families); no filter clears the whole branch scope; --all forces the full wipe
248
248
  remits-cli components sync [--safe [--yes]] [--branch <name>] [--data-mode test|prod] [--force-tombstones] [--dry-run] [--create-variant-branch] [--summary] [--changed-only [--changed-since <ref>]] # gated; on trunk = full repo->DB reconcile, on a variant branch = ComponentVariant overlays only
249
+ remits-cli components sync doctor [--branch <name>] [--json] # read-only: local/remote/platform sync facts, staging shadow risk, promotion freshness, recommended next action
249
250
  remits-cli components commit [--yes] [--safe] [--allow-shared-branch] [--create-variant-branch] [--message|-m "msg"] [--data-mode test|prod] [--force-tombstones] [--json [--summary]] # phase 1 merge-stages + compile-validates changed source (never reconciles the lane); on TRUNK refuses before any git write unless --yes; --safe gates variant sync writes
250
251
  remits-cli components branches [--json] # branches carrying committed variants, with counts + drift
251
252
  remits-cli components branch <name> [--json] # one branch: owner account, overridden / added / removed, drift flags, subscribers
@@ -579,6 +580,10 @@ For tests specifically:
579
580
  - `--summary` on `components sync` prints compact counts plus named updated (with the fields that
580
581
  changed), renamed, skipped, removal/tombstone, error, and gate details instead of the full payload. A
581
582
  trunk sync reports an untouched component under `unchanged`, not `updated`.
583
+ - `components sync doctor` does not mutate. Use it when a sync timed out, status/promotion disagree, or
584
+ you need to know whether staged entries would shadow committed-variant verification. It prints local
585
+ `HEAD`, `origin/<branch>`, platform last-sync SHA, overlay freshness, repository mismatch, and a
586
+ recommended next action.
582
587
  - `--json` on `components stage`, `components sync`, `components commit`, and `tool` prints one
583
588
  parseable JSON document to stdout. Safety banners and prose go to stderr, and sync safety-gate failures
584
589
  are reported inside `gateViolations` with a non-zero exit code instead of appending prose after the JSON.
@@ -71,6 +71,9 @@ local working tree** — and:
71
71
  > want it versioned in git, addressable by a stable id, or discoverable by another agent, it is not
72
72
  > auxiliary. Symptom to recognize: *"I added `new_Foo.groovy`, synced, and it neither appeared on the account
73
73
  > nor got renamed."* Check the sidecar's `auxiliary` flag first.
74
+ >
75
+ > For durable Test suites that should be part of the promotion confirmation set, keep `auxiliary: false` and
76
+ > add `regression: true`. The admin Test runner can select only those suites without hand-picking every case.
74
77
 
75
78
  **The single most important consequence:** the id in a component's **filename is load-bearing**. If a file's id
76
79
  no longer matches its DB row (a renumber or move across ids), the next sync will **create a duplicate at the
@@ -90,6 +93,7 @@ every live component present at its real id, and nothing extra.
90
93
  | `remits-cli components stage` (alias: deprecated `push`) | **Redis staging cache only.** Never mutates the DB or git. The safe iteration surface. A plain stage or `--workset` RECONCILES the lane (entries outside the manifest are dropped); `--changed-only` merges. | none for the DB; a lane you share with another agent is reconciled by the first two |
91
94
  | `remits-cli components status` | Reads this lane's staging scope (account/user/branch/workspace) and branch resolution, and lists every other lane on the branch. | none |
92
95
  | `remits-cli components clear` | Clears THIS lane's staging cache without changing DB or git. Never touches another workspace lane. | none |
96
+ | `remits-cli components sync doctor` | Reads local git, platform status, staging freshness, repository match, and promotion/overlay freshness. It is the safe diagnostic when sync state is confusing. | none |
93
97
  | `remits-cli components sync` **on trunk** | **Server-side git→DB reconcile of the whole account** (create/update/**delete**/rename). Reads the pushed remote; ignores local files. | **high** |
94
98
  | `remits-cli components sync` **on a variant branch** | Writes `ComponentVariant` overlays for that branch only. Never touches trunk rows or the account's trunk branch. When the checkout identifies a subscribing account, the branch-local `account-info.json` is refreshed for that subscriber; `--dry-run` reports the plan without writes. | medium (a missing file becomes a **tombstone** that hides the component from subscribers) |
95
99
  | `remits-cli components commit` | **One shot:** changed-source compile validation (a `--changed-only` MERGE stage, so it never reconciles the lane) + `git add -A` + commit + `git push` + **`components sync`** + `git pull --ff-only`. Blindly stages the *entire* working tree (including any drift) and reconciles it into prod. Inherits the danger of whichever sync mode the branch selects. | **highest on trunk** |
@@ -64,6 +64,17 @@ source-layer facts the command observed. A staged test packet is proof of the st
64
64
  is proof of a source transition; a token packet is proof of the browser token's resolution tuple. Those
65
65
  are not interchangeable.
66
66
 
67
+ Use proof words narrowly:
68
+
69
+ | Evidence | What it proves | What it does NOT prove |
70
+ |---|---|---|
71
+ | `components stage --workset` | The platform accepted the staged workset and compile-checked changed runtime source | Any committed component changed, or any user journey works |
72
+ | `test run` with staged entries | The selected Test passed against this staging lane/world | The committed variant/trunk source passes |
73
+ | `components sync` / sync packet | Durable source moved from the git remote into trunk rows or variant overlays | Browser/upload behavior, or repeated live AI consistency |
74
+ | `components promotion` / `sync doctor` | Which remote SHA and overlay SHA the platform reports now | That a workflow was exercised after the overlay advanced |
75
+ | `token` / browser flow | The browser entry point resolved a host/account/lane/source tuple and the journey worked there | Persisted document fields are stable or equal across repeated runs |
76
+ | `corpus consistency` / repeated live measurement | The same artifact's recorded metrics were stable or unstable across runs in that world | A mocked seam suite caught all customer-facing variance |
77
+
67
78
  Use `remits-cli verify report` before summarizing the work. It will say when the evidence only covered
68
79
  staged source, when committed variant/trunk proof is missing, when the manifest world does not match the
69
80
  packet world, or when later packet facts made an earlier proof stale. Packets carry a lane content hash,
@@ -159,6 +159,11 @@ Two ways to prove it:
159
159
  Use a Test when the behavior can be asserted programmatically, a browser when the change is visual.
160
160
  Often both.
161
161
 
162
+ For Embeddables, prefer `playwright-cli` over `curl`. A `curl` request can confirm the server returned
163
+ something or expose a JSON/action payload, but it is not evidence that the user-facing page works:
164
+ JavaScript, Alpine initialization, assets, cookies/session state, redirects, file controls, iframe/embed
165
+ placement, and websocket updates all require a browser.
166
+
162
167
  **Never skip verification.** "Just do it" and "that's fine, commit it" are not evidence. If you genuinely
163
168
  cannot verify — no Test component, no relevant embeddable — say what you would need and ask how the user
164
169
  wants to proceed rather than reporting the work as done.
@@ -515,6 +520,10 @@ playwright-cli fill "#amount" "500.00"
515
520
  playwright-cli eval "() => document.querySelector('.total-amount').textContent"
516
521
  ```
517
522
 
523
+ Use this browser path for Embeddable acceptance. `curl` is acceptable for narrow diagnostics such as
524
+ checking an HTTP status or inspecting a server-rendered payload, but it does not prove the page initializes
525
+ or behaves correctly in a browser.
526
+
518
527
  The `testMode` metadata confirms you're testing against staged changes, not production.
519
528
 
520
529
  **Two different token keys come back, for two different jobs.** When `--path` resolves to an
@@ -547,13 +556,20 @@ remits-cli tool --name "mcp_firestore_search" --input '{"accountId": <ID>, "coll
547
556
 
548
557
  For long-running backend verification, use the Action runner's own async mode (`executionMode:"async"`) and
549
558
  poll by `actionRunId` rather than holding a single request open (see `command-reference.md` → *Tool Execution Lifecycle* for why not
550
- to also stack the CLI `--async` flag):
559
+ to also stack the CLI `--async` flag). For job-style Actions where you need durable Event lifecycle,
560
+ recovery, or interruption, use `executionMode:"event"` instead and poll the same way:
551
561
 
552
562
  ```bash
553
563
  remits-cli tool --name "mcp_run_action" --input '{"accountId": <ID>, "actionId": <ACTION_ID>, "executionMode": "async", "actionInput": {...}}' --data-mode test
554
564
  # then poll: {"controlAction":"status","accountId": <ID>, "actionRunId":"<actionRunId>"}
555
565
  ```
556
566
 
567
+ Read the returned world and lifecycle fields before deciding what happened. `dataMode` is the data lane
568
+ the run actually used; `componentSource` / `componentBranch` / `stagingLane` say which source world ran.
569
+ For event mode, `awaiting_delivery` means the Event exists but has not been claimed yet, not that the
570
+ Action failed. If that state persists, use `mcp_event_diagnostics` on the returned `eventId` instead of
571
+ guessing that the queue, Action source, or data lane is wrong.
572
+
557
573
  #### Step 5: Iterate If Needed
558
574
 
559
575
  If verification reveals issues, repeat the loop: **edit → stage → run**. Every iteration must include a fresh `remits-cli components stage` after your edits and before the next test run. Never run a test immediately after editing without staging first — the platform will execute the previous version, not your latest changes.
@@ -630,12 +646,17 @@ IDs or when unexpected deletes/renumbers are present.
630
646
  Only after those checks pass, and only when the user intends to promote the repo to the platform database:
631
647
 
632
648
  ```bash
633
- # On a VARIANT branch — the recommended path. Dry-runs first and refuses a surprising plan.
649
+ # On a VARIANT branch in an agent/noninteractive session — dry-runs first, refuses surprises,
650
+ # and skips only the confirmation prompt, not the gates.
651
+ remits-cli components sync --safe --yes
652
+ git pull --ff-only origin <branch>
653
+
654
+ # On a VARIANT branch in a human interactive shell — same gates, then asks before writing.
634
655
  remits-cli components sync --safe
635
656
  git pull --ff-only origin <branch>
636
657
 
637
658
  # On TRUNK — there is no plan to gate, so --safe explains what a trunk reconcile does and needs --yes.
638
- remits-cli components sync
659
+ remits-cli components sync --safe --yes
639
660
  git pull --ff-only origin <branch>
640
661
  ```
641
662
 
@@ -655,14 +676,29 @@ remits-cli components sync --safe --expected-removed action:50
655
676
 
656
677
  `--force-tombstones` stays explicit and human-owned. Never pass it to get past a refusal.
657
678
 
679
+ If sync state is confusing, use the read-only diagnostic instead of building shell loops:
680
+
681
+ ```bash
682
+ remits-cli components sync doctor
683
+ remits-cli components promotion --branch <branch> --no-fail
684
+ ```
685
+
686
+ `components sync doctor` assembles local `HEAD`, `origin/<branch>`, the platform's last synced SHA,
687
+ promotion freshness, staging shadow risk, repository mismatch, and a recommended next action. A client
688
+ timeout does **not** prove the sync failed; it means the result is unknown. Observe before retrying.
689
+ Never put `components sync` inside a polling loop, and do not use shell detach tricks (`setsid`, `nohup`,
690
+ `disown`, `tmux`, ...). A loop may read status/promotion/doctor; it must never start writes.
691
+
658
692
  ##### Verifying the COMMITTED variant, not your staging
659
693
 
660
694
  After a sync, staged entries still win for CLI-scoped runs, so a test that passes may be testing your
661
- staging rather than what you just committed. Clear the lane first:
695
+ staging rather than what you just committed. Clear the lane first, then verify the subscriber/variant
696
+ world explicitly:
662
697
 
663
698
  ```bash
664
699
  remits-cli components clear --all
665
700
  remits-cli test run --test <id-or-name> --as-account <subscriber-id>
701
+ remits-cli components sync doctor
666
702
  ```
667
703
 
668
704
  `remits-cli components sync` is the authoritative platform-sync step. It does not perform local git operations,
@@ -485,9 +485,23 @@ same way (`controlAction:"status"` + `actionRunId`) — the status resolves the
485
485
  remits-cli tool --account-id 21 --as-account 49 --target-account 49 --name mcp_run_action --input '{"actionId":200,"executionMode":"event","actionRunId":"my-stable-run-id","actionInput":{}}' --data-mode prod
486
486
  ```
487
487
 
488
- Returned fields on the async/event start: `actionRunId`, `status:"running"`, `executionMode`,
489
- `threadGroupingId`, `componentSource`, `componentSignature`, and (event mode) `eventId`/`eventStatus`. The
490
- `status` poll adds `result` on completion, or `message`/`error` on failure.
488
+ Event-mode start means "the Event was created and dispatched", not "the Action has finished". Returned
489
+ fields on the async/event start: `actionRunId`, `status:"running"`, `executionMode`, `threadGroupingId`,
490
+ `componentSource`, `componentSignature`, and (event mode) `eventId`/`eventStatus` plus `dispatchMode` when
491
+ available. The `status` poll adds `result` on async completion, or `message`/`error` on failure.
492
+
493
+ Event-mode status values to read deliberately:
494
+
495
+ | `status` | Meaning | What to do |
496
+ |---|---|---|
497
+ | `awaiting_delivery` | The backing Event exists but has not been claimed by a worker yet (`eventStatus` is usually `PENDING`/`QUEUED`, `eventStartTime` is null) | Wait briefly, then inspect `eventDelivery` / `eventNode`; use `mcp_event_diagnostics` if it stays there |
498
+ | `running` | The Event was claimed and is processing | Wait or inspect logs/traces by `threadGroupingId` |
499
+ | `completed` | The backing Event reached `SUCCESS` | Read the Action's persisted outputs / activity trail |
500
+ | `failed` | The backing Event reached `FAILURE`, `CANCELED`, or `INTERRUPTED` | Read `eventStatus`, `errorMessage`, alerts, and `mcp_event_diagnostics` |
501
+
502
+ If an old run is stuck at `QUEUED` and never reports `awaiting_delivery` or fresh delivery fields, start a
503
+ new event-mode run after confirming the System Account tools are synced. Do not keep polling a run that was
504
+ created by an older tool version whose dispatch path already missed delivery.
491
505
 
492
506
  Every response (describe, direct, async start, and failures) also states the world the run resolved:
493
507
  `executionAccountId`, `componentOwnerAccountId`, `componentSource` (`staged` / `variant` / `db`),
@@ -501,7 +515,7 @@ closest existing names; check that the lane named there is the one you staged in
501
515
 
502
516
  > **In event mode the EVENT is the source of truth, not the promise.** `status` is driven by the Event's
503
517
  > own state; the value the dispatch call returned is reported separately as `dispatchResult` and is **not**
504
- > the Action's result. Read `eventStatus` for the outcome.
518
+ > the Action's result. Read `eventStatus`, `eventStartTime`, and `eventDelivery` for the lifecycle outcome.
505
519
 
506
520
  #### Stopping a run — `controlAction:'interrupt'`
507
521
 
@@ -28,6 +28,9 @@
28
28
  | Tool parameters rejected | Tool schemas may be cached. Run `remits-cli tools` to refresh `.remits-cli/shared/tools/tools.json` with latest schemas. |
29
29
  | Tool response missing | Run `remits-cli doctor local-state`, then check `./.remits-cli/actors/<local-agent>/tool-responses/` and any legacy flat `./.remits-cli/tool-responses/` fallback it reports. |
30
30
  | Long-running tool times out | For `mcp_run_action`/`mcp_run_agent`, use the tool's own `executionMode:"async"` and poll with a `controlAction:"status"` call (see `command-reference.md` → *Tool Execution Lifecycle*). For other long tools without their own async, use `remits-cli tool --async true` and poll `remits-cli tool status --call-id <callId>`. `--timeout-ms` only adjusts the per-request HTTP timeout; it is not a substitute for async. |
31
+ | `mcp_run_action` event-mode status is `awaiting_delivery` | The backing Event row exists but no worker has claimed it yet. This is a lifecycle state, not an Action failure and not proof of the wrong data lane. Poll once or twice; if it persists, run `mcp_event_diagnostics` with the returned `eventId` and inspect `eventDelivery`, `eventStartTime`, `eventNode`, and queue/action-node health. |
32
+ | An old `mcp_run_action` event-mode run is stuck `QUEUED` | Do not keep trying to recover the stale run in place. Confirm the System Account tools are synced, start a fresh run with a stable `actionRunId`, and poll that. Interrupt/cancel the stale Event if it would confuse later investigation. |
33
+ | `components sync` timed out, or you are unsure whether overlays advanced | Treat the result as **unknown**, not failed. Run `remits-cli components sync doctor` and `remits-cli components promotion --branch <branch> --no-fail`. Do not put `components sync` in a polling loop and do not use shell detach tricks; observation may poll, writes may not. |
31
34
  | Need to continue tracking a long Action or Agent after terminal disconnect | Read `./.remits-cli/actors/<local-agent>/tool-responses/<callId>.json` for the returned `actionRunId`/`agentRunId`/`sessionId`/`threadGroupingId` (or the legacy flat fallback if `doctor local-state` reports it there). Re-poll the run with a `controlAction:"status"` call carrying that `actionRunId`/`agentRunId`, or inspect the agent session via `mcp_ai_session_search` with the `sessionId`. |
32
35
  | Staged change has no effect in a live (non-CLI) run | Staged overrides resolve only under a CLI TestMode (`branchName`+`cliUserId`). Live webhooks and other non-CLI runtime paths still use the DB (trunk, or the account's subscribed variant). `commit` to make it durable. See `component-resolution.md`. |
33
36
  | Need to know an account's shape (role, type, parents, namespace, branch, host/login routes) | Read `resolution` — from the repo's `account-info.json`, or `mcp_account_user_admin` `action:'account'` (cheap), or `mcp_account_view` (full inventory): `role`/`summary`, `type`, `resolvedDatabaseName`, `domainName`/`resolvedDomainName`, `authPath`/`targetPath`, `relationships`, `componentBranch`, plus top-level `componentBranches`. Never infer structure from the account's name. |