@skrr-ai/cli 0.1.101 → 0.1.103
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/dist/base-command.js +24 -0
- package/dist/commands/agent/message/send.js +6 -2
- package/dist/commands/agents/triggers/create.js +7 -17
- package/dist/commands/agents/triggers/update.js +41 -28
- package/dist/commands/commitments/control/apply.d.ts +15 -0
- package/dist/commands/commitments/control/apply.js +36 -0
- package/dist/commands/commitments/control/preview.d.ts +17 -0
- package/dist/commands/commitments/control/preview.js +43 -0
- package/dist/commands/commitments/control/show.d.ts +12 -0
- package/dist/commands/commitments/control/show.js +29 -0
- package/dist/commands/commitments/create.js +5 -2
- package/dist/commands/commitments/observation/probe.d.ts +15 -0
- package/dist/commands/commitments/observation/probe.js +46 -0
- package/dist/commands/commitments/observation/summary.d.ts +9 -0
- package/dist/commands/commitments/observation/summary.js +31 -0
- package/dist/commands/commitments/preflight.js +18 -1
- package/dist/commands/commitments/readiness/show.d.ts +12 -0
- package/dist/commands/commitments/readiness/show.js +29 -0
- package/dist/commands/commitments/readiness/verify.d.ts +13 -0
- package/dist/commands/commitments/readiness/verify.js +33 -0
- package/dist/commands/commitments/roster.d.ts +15 -0
- package/dist/commands/commitments/roster.js +39 -0
- package/dist/commands/commitments/show.js +1 -1
- package/dist/commands/commitments/status.d.ts +17 -0
- package/dist/commands/commitments/status.js +41 -0
- package/dist/commands/commitments/update.js +23 -9
- package/dist/commands/commitments/work-plan.d.ts +11 -0
- package/dist/commands/commitments/work-plan.js +36 -0
- package/dist/commands/compass/effective-policy.d.ts +12 -0
- package/dist/commands/compass/effective-policy.js +34 -0
- package/dist/commands/compass/update.d.ts +12 -0
- package/dist/commands/compass/update.js +27 -0
- package/dist/commands/daemon/logs.d.ts +39 -1
- package/dist/commands/daemon/logs.js +140 -13
- package/dist/commands/followups/checkin.js +46 -3
- package/dist/commands/machines/dedicated/exec.js +3 -1
- package/dist/commands/proposals/discard.d.ts +4 -0
- package/dist/commands/proposals/discard.js +3 -0
- package/dist/commands/proposals/keep.d.ts +4 -0
- package/dist/commands/proposals/keep.js +3 -0
- package/dist/commands/proposals/snooze.d.ts +4 -0
- package/dist/commands/proposals/snooze.js +3 -0
- package/dist/commands/spaces/workflows/cancel-node.d.ts +16 -0
- package/dist/commands/spaces/workflows/cancel-node.js +44 -0
- package/dist/commands/spaces/workflows/cancel.d.ts +40 -0
- package/dist/commands/spaces/workflows/cancel.js +109 -3
- package/dist/commands/spaces/workflows/run.d.ts +9 -0
- package/dist/commands/spaces/workflows/run.js +38 -0
- package/dist/commands/tasks/approvals/resolve.d.ts +12 -0
- package/dist/commands/tasks/approvals/resolve.js +21 -5
- package/dist/commands/tasks/schedule.js +1 -1
- package/dist/commands/triggers/capabilities.js +7 -0
- package/dist/commands/triggers/create.js +12 -18
- package/dist/commands/triggers/fire.js +3 -0
- package/dist/commands/triggers/update.js +41 -27
- package/dist/commands/widgets/check.d.ts +39 -0
- package/dist/commands/widgets/check.js +179 -0
- package/dist/lib/agent-mailbox.d.ts +0 -1
- package/dist/lib/agent-mailbox.js +0 -1
- package/dist/lib/commitment-loop-status.d.ts +16 -0
- package/dist/lib/commitment-loop-status.js +164 -0
- package/dist/lib/commitments.d.ts +47 -1
- package/dist/lib/commitments.js +218 -6
- package/dist/lib/compasses.d.ts +3 -0
- package/dist/lib/compasses.js +1 -0
- package/dist/lib/delegated-cli.d.ts +23 -1
- package/dist/lib/delegated-cli.js +61 -4
- package/dist/lib/first-party-harness.d.ts +1 -1
- package/dist/lib/first-party-harness.js +1 -1
- package/dist/lib/responsibility-controls.d.ts +11 -0
- package/dist/lib/responsibility-controls.js +119 -0
- package/dist/lib/session-task-write.d.ts +13 -0
- package/dist/lib/session-task-write.js +63 -3
- package/dist/lib/stewardship-output.js +13 -2
- package/dist/lib/triggers.d.ts +26 -0
- package/dist/lib/triggers.js +134 -1
- package/dist/lib/widgetCheck.d.ts +123 -0
- package/dist/lib/widgetCheck.js +998 -0
- package/dist/lib/widgetStaticScan.d.ts +111 -0
- package/dist/lib/widgetStaticScan.js +833 -0
- package/dist/lib/workflow-node-control.d.ts +10 -0
- package/dist/lib/workflow-node-control.js +76 -0
- package/dist/lib/workspaces.d.ts +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +9 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +12 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +9 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +12 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +11924 -8704
- package/oclif.manifest.json +1487 -396
- package/package.json +11 -2
package/dist/lib/commitments.js
CHANGED
|
@@ -9,6 +9,7 @@ exports.parseCommitmentConcurrencyLimit = parseCommitmentConcurrencyLimit;
|
|
|
9
9
|
exports.commitmentConcurrencyEnvelopePatch = commitmentConcurrencyEnvelopePatch;
|
|
10
10
|
exports.describeCommitmentConcurrency = describeCommitmentConcurrency;
|
|
11
11
|
exports.parseIntervalToMs = parseIntervalToMs;
|
|
12
|
+
exports.normalizeWatchedSourceDeclarations = normalizeWatchedSourceDeclarations;
|
|
12
13
|
exports.buildWatchedSources = buildWatchedSources;
|
|
13
14
|
exports.describeEvidenceHealth = describeEvidenceHealth;
|
|
14
15
|
exports.describeIntervalMs = describeIntervalMs;
|
|
@@ -17,14 +18,17 @@ exports.cadenceLabel = cadenceLabel;
|
|
|
17
18
|
exports.renderCommitmentList = renderCommitmentList;
|
|
18
19
|
exports.extractStandstillFromCheck = extractStandstillFromCheck;
|
|
19
20
|
exports.describeExecutorBlocked = describeExecutorBlocked;
|
|
21
|
+
exports.withLiveAssessment = withLiveAssessment;
|
|
20
22
|
exports.renderCommitmentSummary = renderCommitmentSummary;
|
|
21
23
|
exports.describeWakeDecision = describeWakeDecision;
|
|
22
24
|
exports.renderCheckList = renderCheckList;
|
|
23
25
|
exports.renderCommitmentLedger = renderCommitmentLedger;
|
|
24
26
|
exports.renderCommitmentTrace = renderCommitmentTrace;
|
|
27
|
+
exports.renderProactivenessScorecard = renderProactivenessScorecard;
|
|
25
28
|
exports.formatCommitmentCycles = formatCommitmentCycles;
|
|
26
29
|
exports.formatCommitmentExplanation = formatCommitmentExplanation;
|
|
27
30
|
exports.renderListPageHint = renderListPageHint;
|
|
31
|
+
exports.describeObservationArtifacts = describeObservationArtifacts;
|
|
28
32
|
exports.renderCommitmentObservations = renderCommitmentObservations;
|
|
29
33
|
exports.renderCommitmentHypotheses = renderCommitmentHypotheses;
|
|
30
34
|
exports.renderCommitmentLineage = renderCommitmentLineage;
|
|
@@ -33,6 +37,7 @@ exports.renderCommitmentCases = renderCommitmentCases;
|
|
|
33
37
|
exports.renderCommitmentCaseDetail = renderCommitmentCaseDetail;
|
|
34
38
|
exports.renderCommitmentRemediationProposals = renderCommitmentRemediationProposals;
|
|
35
39
|
exports.renderCommitmentRemediationProposal = renderCommitmentRemediationProposal;
|
|
40
|
+
const node_util_1 = require("node:util");
|
|
36
41
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
37
42
|
/**
|
|
38
43
|
* commitments.ts — request-based client for the Commitments subsystem
|
|
@@ -132,12 +137,25 @@ exports.commitmentApi = {
|
|
|
132
137
|
* swept, how stale that is, and the tally it last persisted. Aggregate only.
|
|
133
138
|
*/
|
|
134
139
|
livenessSweep: () => data_provider_2.request.get('/api/commitments/liveness/sweep'),
|
|
140
|
+
/** One agent's Commitments, system roles included, each with its loop status. */
|
|
141
|
+
roster: (agentId) => data_provider_2.request.get((0, triggers_1.withQuery)('/api/commitments/roster', { agentId })),
|
|
142
|
+
/** Is this Commitment's loop installed and working. */
|
|
143
|
+
loopStatus: (id) => data_provider_2.request.get(`${base(id)}/loop-status`),
|
|
135
144
|
manifest: (id) => data_provider_2.request.get(`${base(id)}/manifest`),
|
|
136
145
|
import: (body) => data_provider_2.request.post('/api/commitments/import', body),
|
|
137
146
|
fork: (id, body) => data_provider_2.request.post(`${base(id)}/fork`, body),
|
|
138
147
|
// GET /:id returns `{ commitment, latestCheck }` — NOT a bare contract.
|
|
139
148
|
// Unwrap before rendering, or every field reads as undefined.
|
|
140
149
|
get: (id) => data_provider_2.request.get(base(id)),
|
|
150
|
+
controlPreview: (id, query) => data_provider_2.request.get((0, triggers_1.withQuery)(`${base(id)}/control-preview`, query)),
|
|
151
|
+
applyControl: (id, body) => data_provider_2.request.post(`${base(id)}/control`, body),
|
|
152
|
+
controlReceipt: (id, controlId) => data_provider_2.request.get(`${base(id)}/control/${encodeURIComponent(controlId)}`),
|
|
153
|
+
workPlan: (id) => data_provider_2.request.get(`${base(id)}/work-plan`),
|
|
154
|
+
observationSummary: (query = {}) => data_provider_2.request.get((0, triggers_1.withQuery)('/api/commitments/observation-summary', query)),
|
|
155
|
+
draftObservationProbe: (body) => data_provider_2.request.post('/api/commitments/observation-probe', body),
|
|
156
|
+
observationProbe: (id, body = {}) => data_provider_2.request.post(`${base(id)}/observation-probe`, body),
|
|
157
|
+
verifyReadiness: (id, body) => data_provider_2.request.post(`${base(id)}/readiness-receipt`, body),
|
|
158
|
+
readinessReceipt: (id, receiptId) => data_provider_2.request.get(`${base(id)}/readiness-receipt/${encodeURIComponent(receiptId)}`),
|
|
141
159
|
create: (body) => data_provider_2.request.post('/api/commitments', body),
|
|
142
160
|
update: (id, body) => data_provider_2.request.patch(base(id), body),
|
|
143
161
|
draftPreflight: (body) => data_provider_2.request.post('/api/commitments/preflight', body),
|
|
@@ -496,11 +514,35 @@ function parseIntervalToMs(input) {
|
|
|
496
514
|
}
|
|
497
515
|
return ms;
|
|
498
516
|
}
|
|
517
|
+
/** Keep the canonical one-binding-per-source contract without discarding conflicting intent. */
|
|
518
|
+
function normalizeWatchedSourceDeclarations(declarations) {
|
|
519
|
+
const bySource = new Map();
|
|
520
|
+
const normalized = [];
|
|
521
|
+
for (const binding of declarations) {
|
|
522
|
+
if (!binding ||
|
|
523
|
+
typeof binding !== 'object' ||
|
|
524
|
+
!('source' in binding) ||
|
|
525
|
+
typeof binding.source !== 'string') {
|
|
526
|
+
normalized.push(binding); // The API owns validation of the remaining DTO.
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
if (bySource.has(binding.source)) {
|
|
530
|
+
if (!(0, node_util_1.isDeepStrictEqual)(bySource.get(binding.source), binding)) {
|
|
531
|
+
throw new Error(`Watched source "${binding.source}" has conflicting declarations. ` +
|
|
532
|
+
'Choose one complete binding per source.');
|
|
533
|
+
}
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
bySource.set(binding.source, binding);
|
|
537
|
+
normalized.push(binding);
|
|
538
|
+
}
|
|
539
|
+
return normalized;
|
|
540
|
+
}
|
|
499
541
|
function buildWatchedSources(inputs) {
|
|
500
542
|
const watched = (inputs.watch ?? []).filter(Boolean);
|
|
501
543
|
const metricKeys = (inputs.metricKey ?? []).filter(Boolean);
|
|
502
|
-
const spaceIds = (inputs.spaceIds ?? []).filter(Boolean);
|
|
503
|
-
const monitorIds = (inputs.monitorIds ?? []).filter(Boolean);
|
|
544
|
+
const spaceIds = [...new Set((inputs.spaceIds ?? []).filter(Boolean))];
|
|
545
|
+
const monitorIds = [...new Set((inputs.monitorIds ?? []).filter(Boolean))];
|
|
504
546
|
const githubEvents = (inputs.githubEvents ?? []).filter(Boolean);
|
|
505
547
|
const githubRepository = String(inputs.githubRepository || '')
|
|
506
548
|
.trim()
|
|
@@ -584,7 +626,7 @@ function buildWatchedSources(inputs) {
|
|
|
584
626
|
rows.push({ source, enabled: true });
|
|
585
627
|
}
|
|
586
628
|
}
|
|
587
|
-
return rows;
|
|
629
|
+
return normalizeWatchedSourceDeclarations(rows);
|
|
588
630
|
}
|
|
589
631
|
/**
|
|
590
632
|
* One line an author can act on, or nothing.
|
|
@@ -613,10 +655,33 @@ function describeEvidenceHealth(health, criteriaCount) {
|
|
|
613
655
|
return `unevidenceable_criteria${detail} — the criteria below cannot be judged from what this commitment observes; rewrite them or attach a Measure`;
|
|
614
656
|
}
|
|
615
657
|
if (state === 'degraded_observability') {
|
|
616
|
-
return `degraded_observability${detail} —
|
|
658
|
+
return `degraded_observability${detail} — ${describeDegradedObservability(reasons)}`;
|
|
617
659
|
}
|
|
618
660
|
return `${state}${detail}`;
|
|
619
661
|
}
|
|
662
|
+
/**
|
|
663
|
+
* What a `degraded_observability` verdict actually says, from its reasons.
|
|
664
|
+
*
|
|
665
|
+
* The state is a catch-all on the server (anything that is not purely an
|
|
666
|
+
* authoring problem lands here), so the copy used to assert "an evaluator
|
|
667
|
+
* could not run; this is a fault" for every case — including a commitment
|
|
668
|
+
* whose first run had simply not reported yet (OSK-15989). Only the
|
|
669
|
+
* `*_evidence_unavailable` reasons say an evaluator could not run; the rest
|
|
670
|
+
* say which part of the reading was missing, and that is what this names.
|
|
671
|
+
*/
|
|
672
|
+
function describeDegradedObservability(reasons) {
|
|
673
|
+
const has = (reason) => reasons.includes(reason);
|
|
674
|
+
if (has('state_evidence_unavailable') || has('outcome_evidence_unavailable')) {
|
|
675
|
+
return 'an evaluator could not run; this is a fault, not a criteria problem';
|
|
676
|
+
}
|
|
677
|
+
if (has('access_issues') || has('source_warning_or_error') || has('no_healthy_source')) {
|
|
678
|
+
return 'not every configured source could be read on the last check (`skrr commitments doctor <id>` names the next step)';
|
|
679
|
+
}
|
|
680
|
+
if (has('evidence_stale')) {
|
|
681
|
+
return "the freshest evidence is older than this commitment's freshness expectation";
|
|
682
|
+
}
|
|
683
|
+
return 'the last check could not see everything it needs to judge this commitment';
|
|
684
|
+
}
|
|
620
685
|
/** Render an interval back as a compact human string. */
|
|
621
686
|
function describeIntervalMs(ms) {
|
|
622
687
|
if (!ms || !Number.isFinite(ms))
|
|
@@ -794,6 +859,20 @@ function describeExecutorBlocked(commitment) {
|
|
|
794
859
|
: reason || 'blocked';
|
|
795
860
|
return `${label}${until ? ` until ${until}` : ''}`;
|
|
796
861
|
}
|
|
862
|
+
/**
|
|
863
|
+
* The commitment `show` should render: its stored contract, with the
|
|
864
|
+
* assessment replaced by the one the operating state recomputed for this read
|
|
865
|
+
* when the server sent one (OSK-15989). The stored `commitment.assessment` is
|
|
866
|
+
* what the LAST check wrote; while a run is in flight it can still say
|
|
867
|
+
* `blocked`, and printing it above "Agent is working" made the header
|
|
868
|
+
* contradict the rest of the page. `doctor` already reads the live one first.
|
|
869
|
+
*/
|
|
870
|
+
function withLiveAssessment(commitment, operatingState) {
|
|
871
|
+
const live = operatingState?.assessment;
|
|
872
|
+
if (!live || typeof live !== 'object')
|
|
873
|
+
return commitment;
|
|
874
|
+
return { ...commitment, assessment: live };
|
|
875
|
+
}
|
|
797
876
|
function renderCommitmentSummary(commitment, log,
|
|
798
877
|
/**
|
|
799
878
|
* The most recent check, when the caller has one. Optional because pause /
|
|
@@ -1162,10 +1241,103 @@ function renderCommitmentTrace(trace, log) {
|
|
|
1162
1241
|
{ key: 'stop', header: 'STOPPED AT', maxWidth: 48 },
|
|
1163
1242
|
{ key: 'summary', header: 'SUMMARY', maxWidth: 72 },
|
|
1164
1243
|
], 'No trace events recorded.', log);
|
|
1244
|
+
const scorecard = record(trace.proactiveness);
|
|
1245
|
+
if (scorecard)
|
|
1246
|
+
renderProactivenessScorecard(scorecard, log);
|
|
1165
1247
|
if (trace.has_more === true && trace.next_cursor) {
|
|
1166
1248
|
log(`More events are available; continue with --cursor ${text(trace.next_cursor)}.`);
|
|
1167
1249
|
}
|
|
1168
1250
|
}
|
|
1251
|
+
/*
|
|
1252
|
+
* Display order and words for `workClassification.counts`. The names are owned
|
|
1253
|
+
* by the API scorecard (`COMMITMENT_CHECK_WORK_CLASSIFICATIONS` in
|
|
1254
|
+
* CommitmentProactivenessMetrics); an unknown name still prints, by its key.
|
|
1255
|
+
*/
|
|
1256
|
+
const WORK_CLASSIFICATION_LABELS = {
|
|
1257
|
+
justified_source_change: 'source changed',
|
|
1258
|
+
justified_time_boundary: 'review/deadline due',
|
|
1259
|
+
justified_evidence: 'evidence refresh',
|
|
1260
|
+
work_item: 'open work item',
|
|
1261
|
+
instruction: 'instruction',
|
|
1262
|
+
repeat_identical_evidence: 'REPEAT on identical evidence',
|
|
1263
|
+
unknown_basis: 'basis not recorded',
|
|
1264
|
+
};
|
|
1265
|
+
const SCORECARD_METRICS = [
|
|
1266
|
+
{ key: 'justifiedDecisionRate', label: 'Justified work', kind: 'ratio' },
|
|
1267
|
+
{ key: 'repeatDecisionRate', label: 'Repeat decisions', kind: 'ratio' },
|
|
1268
|
+
{ key: 'duplicateInvestigationRate', label: 'Duplicate investigations', kind: 'ratio' },
|
|
1269
|
+
{ key: 'usefulActionYield', label: 'Useful action yield', kind: 'ratio' },
|
|
1270
|
+
{ key: 'notificationPrecision', label: 'Notification precision', kind: 'ratio' },
|
|
1271
|
+
{ key: 'taskClosureRate', label: 'Task closure', kind: 'ratio' },
|
|
1272
|
+
{ key: 'provenanceCompleteness', label: 'Provenance complete', kind: 'ratio' },
|
|
1273
|
+
{ key: 'materialDetectionLatencyMs', label: 'Detection latency p50', kind: 'latency' },
|
|
1274
|
+
{ key: 'evidenceFreshnessMs', label: 'Evidence age p50', kind: 'latency' },
|
|
1275
|
+
];
|
|
1276
|
+
function scorecardValue(value, kind) {
|
|
1277
|
+
if (kind === 'ratio') {
|
|
1278
|
+
return typeof value === 'number' && Number.isFinite(value)
|
|
1279
|
+
? `${Math.round(value * 100)}%`
|
|
1280
|
+
: '-';
|
|
1281
|
+
}
|
|
1282
|
+
const p50 = record(value)?.p50;
|
|
1283
|
+
if (typeof p50 !== 'number' || !Number.isFinite(p50))
|
|
1284
|
+
return '-';
|
|
1285
|
+
return p50 >= 60_000 ? `${Math.round(p50 / 60_000)}m` : `${Math.round(p50 / 1000)}s`;
|
|
1286
|
+
}
|
|
1287
|
+
/** The qualifier that keeps a number from claiming more than its samples can. */
|
|
1288
|
+
function scorecardQualifier(coverage) {
|
|
1289
|
+
if (!coverage)
|
|
1290
|
+
return '';
|
|
1291
|
+
const samples = typeof coverage.samples === 'number' ? coverage.samples : null;
|
|
1292
|
+
const parts = [];
|
|
1293
|
+
if (samples !== null)
|
|
1294
|
+
parts.push(`${samples} sample${samples === 1 ? '' : 's'}`);
|
|
1295
|
+
if (coverage.reliability === 'no_samples')
|
|
1296
|
+
return '(no samples)';
|
|
1297
|
+
if (coverage.reliability === 'mostly_unknown_basis') {
|
|
1298
|
+
parts.push(`ESTIMATE: ${text(coverage.unknownBasis, '?')} with no recorded basis`);
|
|
1299
|
+
}
|
|
1300
|
+
else if (typeof coverage.unknownBasis === 'number' && coverage.unknownBasis > 0) {
|
|
1301
|
+
parts.push(`${coverage.unknownBasis} with no recorded basis`);
|
|
1302
|
+
}
|
|
1303
|
+
if (typeof coverage.partialObservation === 'number' && coverage.partialObservation > 0) {
|
|
1304
|
+
parts.push(`${coverage.partialObservation} partial observation`);
|
|
1305
|
+
}
|
|
1306
|
+
if (coverage.partialWindow === true)
|
|
1307
|
+
parts.push('partial page');
|
|
1308
|
+
return parts.length ? `(${parts.join('; ')})` : '';
|
|
1309
|
+
}
|
|
1310
|
+
function renderProactivenessScorecard(scorecard, log) {
|
|
1311
|
+
const coverage = record(scorecard.coverage);
|
|
1312
|
+
const metrics = record(scorecard.metrics) || {};
|
|
1313
|
+
const metricCoverage = record(scorecard.metricCoverage) || {};
|
|
1314
|
+
log('');
|
|
1315
|
+
log(coverage?.partialWindow === true || record(scorecard.window)?.partial === true
|
|
1316
|
+
? 'Proactiveness (this page only — not the whole window)'
|
|
1317
|
+
: 'Proactiveness');
|
|
1318
|
+
if (coverage) {
|
|
1319
|
+
log(` Checks: ${text(coverage.checks, '0')} read · ${text(coverage.decisioningChecks, '0')} decided work · ` +
|
|
1320
|
+
`${text(coverage.quietChecks, '0')} quiet · ${text(coverage.dedupeSuppressedChecks, '0')} suppressed as duplicate`);
|
|
1321
|
+
}
|
|
1322
|
+
const counts = record(record(scorecard.workClassification)?.counts);
|
|
1323
|
+
if (counts) {
|
|
1324
|
+
const parts = Object.entries(counts)
|
|
1325
|
+
.filter(([, value]) => typeof value === 'number' && value > 0)
|
|
1326
|
+
.map(([key, value]) => `${WORK_CLASSIFICATION_LABELS[key] || key} ${value}`);
|
|
1327
|
+
log(` Why work was decided: ${parts.length ? parts.join(' · ') : 'no decisioning Checks'}`);
|
|
1328
|
+
}
|
|
1329
|
+
for (const metric of SCORECARD_METRICS) {
|
|
1330
|
+
if (!(metric.key in metrics))
|
|
1331
|
+
continue;
|
|
1332
|
+
const qualifier = scorecardQualifier(record(metricCoverage[metric.key]));
|
|
1333
|
+
log(` ${metric.label.padEnd(26)} ${scorecardValue(metrics[metric.key], metric.kind).padStart(5)}` +
|
|
1334
|
+
(qualifier ? ` ${qualifier}` : ''));
|
|
1335
|
+
}
|
|
1336
|
+
for (const note of Array.isArray(coverage?.notes) ? coverage.notes : []) {
|
|
1337
|
+
if (typeof note === 'string' && note)
|
|
1338
|
+
log(` Note: ${note}`);
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1169
1341
|
const CYCLE_GLYPH = {
|
|
1170
1342
|
advanced: '✓',
|
|
1171
1343
|
stopped: '●',
|
|
@@ -1192,7 +1364,12 @@ function cycleWake(cycle) {
|
|
|
1192
1364
|
function cycleSummary(cycle) {
|
|
1193
1365
|
if (cycle.outcome === 'stopped' && cycle.stop) {
|
|
1194
1366
|
const { question, gate, reason, fix, detail } = cycle.stop;
|
|
1195
|
-
|
|
1367
|
+
// A stopped cycle can still have moved something (the Check's own read, a task it
|
|
1368
|
+
// made); say so, or the row reads as if nothing happened (OSK-15408).
|
|
1369
|
+
const moved = cycle.advancementKinds?.length
|
|
1370
|
+
? ` (moved: ${cycle.advancementKinds.join(', ')})`
|
|
1371
|
+
: '';
|
|
1372
|
+
return `${question} ${reason || gate} — ${fix || detail || 'stopped'}${moved}`;
|
|
1196
1373
|
}
|
|
1197
1374
|
if (cycle.outcome === 'advanced') {
|
|
1198
1375
|
return cycle.advancementKinds?.length
|
|
@@ -1288,6 +1465,8 @@ function formatCycleLatency(cycle) {
|
|
|
1288
1465
|
];
|
|
1289
1466
|
}
|
|
1290
1467
|
const ANSWER_GLYPH = { yes: '✓', no: '✗', 'n/a': '–' };
|
|
1468
|
+
/** The mark `cycles` already gives a chosen quiet: the loop looked and decided to say nothing. */
|
|
1469
|
+
const QUIET_ANSWER_GLYPH = CYCLE_GLYPH.quiet;
|
|
1291
1470
|
/** The six questions for the latest Cycle, the first ✗ carrying its fix. */
|
|
1292
1471
|
function formatCommitmentExplanation(explanation) {
|
|
1293
1472
|
const cycle = explanation.cycle;
|
|
@@ -1299,7 +1478,14 @@ function formatCommitmentExplanation(explanation) {
|
|
|
1299
1478
|
if (cycle)
|
|
1300
1479
|
lines.push(...formatCycleLatency(cycle));
|
|
1301
1480
|
for (const answer of explanation.answers || []) {
|
|
1302
|
-
|
|
1481
|
+
// The answer a deliberate quiet stopped at is not a refusal: `cycles` marks
|
|
1482
|
+
// that cycle ○, and a ✗ here would read as a gate (OSK-15403).
|
|
1483
|
+
const glyph = explanation.verdict === 'quiet' &&
|
|
1484
|
+
answer.answer === 'no' &&
|
|
1485
|
+
answer.key === explanation.stoppedAt
|
|
1486
|
+
? QUIET_ANSWER_GLYPH
|
|
1487
|
+
: ANSWER_GLYPH[answer.answer] || '?';
|
|
1488
|
+
lines.push(` ${glyph} ${answer.question} ${answer.because}`);
|
|
1303
1489
|
if (answer.fix)
|
|
1304
1490
|
lines.push(` Fix: ${answer.fix}`);
|
|
1305
1491
|
}
|
|
@@ -1343,6 +1529,30 @@ function yesNo(value) {
|
|
|
1343
1529
|
return 'no';
|
|
1344
1530
|
return '-';
|
|
1345
1531
|
}
|
|
1532
|
+
/**
|
|
1533
|
+
* Whether a row's retained evidence can still be reopened (OSK-16125).
|
|
1534
|
+
*
|
|
1535
|
+
* The count alone read as evidence even when every path pointed into a
|
|
1536
|
+
* worktree the daemon had already deleted. `retained` artifacts are git
|
|
1537
|
+
* locators on the run's delivery commit; `ephemeral` ones cannot be counted
|
|
1538
|
+
* on; `unconfirmed` are locators for a path the run did not change. Refs the
|
|
1539
|
+
* server classified as none of these (URLs, hashes, legacy rows) count as
|
|
1540
|
+
* `other`. `--json` carries each artifact's `ref`, `locator` and `originalRef`.
|
|
1541
|
+
*/
|
|
1542
|
+
function describeObservationArtifacts(artifacts) {
|
|
1543
|
+
const list = Array.isArray(artifacts) ? artifacts : [];
|
|
1544
|
+
if (list.length === 0)
|
|
1545
|
+
return '-';
|
|
1546
|
+
const counts = new Map();
|
|
1547
|
+
for (const artifact of list) {
|
|
1548
|
+
const availability = text(record(artifact)?.availability, 'other');
|
|
1549
|
+
counts.set(availability, (counts.get(availability) || 0) + 1);
|
|
1550
|
+
}
|
|
1551
|
+
return ['retained', 'unconfirmed', 'ephemeral', 'other']
|
|
1552
|
+
.filter((name) => counts.has(name))
|
|
1553
|
+
.map((name) => `${counts.get(name)} ${name}`)
|
|
1554
|
+
.join(', ');
|
|
1555
|
+
}
|
|
1346
1556
|
function renderCommitmentObservations(response, log, options = {}) {
|
|
1347
1557
|
const data = records(response.data);
|
|
1348
1558
|
renderRows(data.map((item) => ({
|
|
@@ -1371,6 +1581,7 @@ function renderCommitmentObservations(response, log, options = {}) {
|
|
|
1371
1581
|
declared: yesNo(item.declared),
|
|
1372
1582
|
access: text(item.accessStatus),
|
|
1373
1583
|
confidence: (0, format_1.formatCell)(item.confidence),
|
|
1584
|
+
artifacts: describeObservationArtifacts(item.artifacts),
|
|
1374
1585
|
})), [
|
|
1375
1586
|
{ key: 'at', header: 'OBSERVED', maxWidth: 24 },
|
|
1376
1587
|
{ key: 'recorded', header: 'RECORDED', maxWidth: 24 },
|
|
@@ -1386,6 +1597,7 @@ function renderCommitmentObservations(response, log, options = {}) {
|
|
|
1386
1597
|
{ key: 'declared', header: 'DECLARED', maxWidth: 9 },
|
|
1387
1598
|
{ key: 'access', header: 'ACCESS', maxWidth: 13 },
|
|
1388
1599
|
{ key: 'confidence', header: 'CONF', maxWidth: 6 },
|
|
1600
|
+
{ key: 'artifacts', header: 'ARTIFACTS', maxWidth: 36 },
|
|
1389
1601
|
], options.emptyMessage || 'No observations recorded.', log);
|
|
1390
1602
|
}
|
|
1391
1603
|
/** Human view for causal hypotheses. */
|
package/dist/lib/compasses.d.ts
CHANGED
|
@@ -88,6 +88,9 @@ export declare const compassApi: {
|
|
|
88
88
|
}>;
|
|
89
89
|
create: (body: Record<string, unknown>) => Promise<Compass>;
|
|
90
90
|
get: (id: string, query: Parameters<typeof withQuery>[1]) => Promise<Compass>;
|
|
91
|
+
effectivePolicy: (id: string, query: {
|
|
92
|
+
agentId: string;
|
|
93
|
+
}) => Promise<Record<string, unknown>>;
|
|
91
94
|
update: (id: string, body: Record<string, unknown>) => Promise<Compass>;
|
|
92
95
|
archive: (id: string, query: Parameters<typeof withQuery>[1]) => Promise<{
|
|
93
96
|
ok: boolean;
|
package/dist/lib/compasses.js
CHANGED
|
@@ -35,6 +35,7 @@ exports.compassApi = {
|
|
|
35
35
|
list: (query) => data_provider_2.request.get((0, triggers_1.withQuery)('/api/compasses', query)),
|
|
36
36
|
create: (body) => data_provider_2.request.post('/api/compasses', body),
|
|
37
37
|
get: (id, query) => data_provider_2.request.get((0, triggers_1.withQuery)(base(id), query)),
|
|
38
|
+
effectivePolicy: (id, query) => data_provider_2.request.get((0, triggers_1.withQuery)(`${base(id)}/effective-policy`, query)),
|
|
38
39
|
update: (id, body) => data_provider_2.request.patch(base(id), body),
|
|
39
40
|
archive: (id, query) => data_provider_2.request.delete((0, triggers_1.withQuery)(base(id), query)),
|
|
40
41
|
revisions: (id, query) => data_provider_2.request.get((0, triggers_1.withQuery)(`${base(id)}/revisions`, query)),
|
|
@@ -2,7 +2,29 @@ import { type ResolvedCredential } from '@skrr-ai/auth-core';
|
|
|
2
2
|
export declare function isDelegatedCliSession(env?: Record<string, string | undefined>): boolean;
|
|
3
3
|
/** Drop the memoized credential. Exported for tests. */
|
|
4
4
|
export declare function resetDelegatedCliCredentialCache(): void;
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* The daemon's delegated broker answered and could not mint, for a reason it
|
|
7
|
+
* named (OSK-12151). `retryable` says whether waiting can help: false when only
|
|
8
|
+
* a new daemon sign-in repairs it (DEVICE_PROOF_PIN_MISMATCH), and then
|
|
9
|
+
* `remedy` is the command to run.
|
|
10
|
+
*/
|
|
11
|
+
export declare class DelegatedCliUnavailableError extends Error {
|
|
12
|
+
readonly retryable: boolean;
|
|
13
|
+
readonly upstreamStatus?: number;
|
|
14
|
+
readonly upstreamCode?: string;
|
|
15
|
+
readonly upstreamReason?: string;
|
|
16
|
+
/** ISO time before which the daemon will not ask the server again. */
|
|
17
|
+
readonly retryAt?: string;
|
|
18
|
+
readonly remedy?: string;
|
|
19
|
+
constructor(message: string, fields: {
|
|
20
|
+
retryable: boolean;
|
|
21
|
+
upstreamStatus?: number;
|
|
22
|
+
upstreamCode?: string;
|
|
23
|
+
upstreamReason?: string;
|
|
24
|
+
retryAt?: string;
|
|
25
|
+
remedy?: string;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
6
28
|
export declare function fetchDelegatedCliCredential(env?: Record<string, string | undefined>, options?: {
|
|
7
29
|
forceRefresh?: boolean;
|
|
8
30
|
}): Promise<ResolvedCredential | null>;
|
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.DelegatedCliUnavailableError = void 0;
|
|
36
37
|
exports.isDelegatedCliSession = isDelegatedCliSession;
|
|
37
38
|
exports.resetDelegatedCliCredentialCache = resetDelegatedCliCredentialCache;
|
|
38
39
|
exports.fetchDelegatedCliCredential = fetchDelegatedCliCredential;
|
|
@@ -198,6 +199,50 @@ function composeUnreachableBrokerMessage(brokerUrl, err) {
|
|
|
198
199
|
(code ? ` (${code})` : ''));
|
|
199
200
|
}
|
|
200
201
|
/** Fetch a full-permission CLI access token from the local daemon. */
|
|
202
|
+
/**
|
|
203
|
+
* Upstream codes the SAME daemon credential will keep getting, so retrying is
|
|
204
|
+
* pointless and the repair is signing the daemon in again (OSK-15990). Mirrors
|
|
205
|
+
* the daemon's `CREDENTIAL_BOUND_CODES`; it is the fallback for a daemon that
|
|
206
|
+
* predates the `retryable` field on its 503.
|
|
207
|
+
*/
|
|
208
|
+
const SIGN_IN_BOUND_CODES = new Set([
|
|
209
|
+
'DEVICE_PROOF_PIN_MISMATCH',
|
|
210
|
+
'CI_TOKEN_REFUSED',
|
|
211
|
+
'SESSION_REVOKED',
|
|
212
|
+
'PRINCIPAL_UNRESOLVED',
|
|
213
|
+
'USER_ID_UNRESOLVED',
|
|
214
|
+
]);
|
|
215
|
+
function isSignInBoundCode(code) {
|
|
216
|
+
return typeof code === 'string' && SIGN_IN_BOUND_CODES.has(code);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* The daemon's delegated broker answered and could not mint, for a reason it
|
|
220
|
+
* named (OSK-12151). `retryable` says whether waiting can help: false when only
|
|
221
|
+
* a new daemon sign-in repairs it (DEVICE_PROOF_PIN_MISMATCH), and then
|
|
222
|
+
* `remedy` is the command to run.
|
|
223
|
+
*/
|
|
224
|
+
class DelegatedCliUnavailableError extends Error {
|
|
225
|
+
retryable;
|
|
226
|
+
upstreamStatus;
|
|
227
|
+
upstreamCode;
|
|
228
|
+
upstreamReason;
|
|
229
|
+
/** ISO time before which the daemon will not ask the server again. */
|
|
230
|
+
retryAt;
|
|
231
|
+
remedy;
|
|
232
|
+
constructor(message, fields) {
|
|
233
|
+
super(message);
|
|
234
|
+
this.name = 'DelegatedCliUnavailableError';
|
|
235
|
+
this.retryable = fields.retryable;
|
|
236
|
+
this.upstreamStatus = fields.upstreamStatus;
|
|
237
|
+
this.upstreamCode = fields.upstreamCode;
|
|
238
|
+
this.upstreamReason = fields.upstreamReason;
|
|
239
|
+
this.retryAt = fields.retryAt;
|
|
240
|
+
this.remedy =
|
|
241
|
+
fields.remedy ??
|
|
242
|
+
(fields.retryable ? undefined : 'run `skrr daemon login` on the machine running the daemon');
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
exports.DelegatedCliUnavailableError = DelegatedCliUnavailableError;
|
|
201
246
|
async function fetchDelegatedCliCredential(env = process.env, options = {}) {
|
|
202
247
|
if (!isDelegatedCliSession(env))
|
|
203
248
|
return null;
|
|
@@ -270,11 +315,23 @@ async function fetchDelegatedCliCredential(env = process.env, options = {}) {
|
|
|
270
315
|
]
|
|
271
316
|
.filter(Boolean)
|
|
272
317
|
.join(' ');
|
|
273
|
-
// The daemon's `message` already names the refusal
|
|
274
|
-
//
|
|
275
|
-
|
|
318
|
+
// The daemon's `message` already names the refusal (and, from a
|
|
319
|
+
// daemon with OSK-16117, when to retry or what to run); fall back to
|
|
320
|
+
// the status and code when an older daemon sent none.
|
|
321
|
+
throw new DelegatedCliUnavailableError(typeof body?.message === 'string' && body.message
|
|
276
322
|
? `the daemon's delegated CLI broker could not mint a credential: ${body.message}`
|
|
277
|
-
: `the skrr server refused the daemon's request for this session's credential (${named})
|
|
323
|
+
: `the skrr server refused the daemon's request for this session's credential (${named}).`, {
|
|
324
|
+
...(typeof body?.upstreamStatus === 'number' ? { upstreamStatus: body.upstreamStatus } : {}),
|
|
325
|
+
...(typeof body?.upstreamCode === 'string' ? { upstreamCode: body.upstreamCode } : {}),
|
|
326
|
+
...(typeof body?.upstreamReason === 'string'
|
|
327
|
+
? { upstreamReason: body.upstreamReason }
|
|
328
|
+
: {}),
|
|
329
|
+
...(typeof body?.retryAt === 'string' ? { retryAt: body.retryAt } : {}),
|
|
330
|
+
...(typeof body?.remedy === 'string' ? { remedy: body.remedy } : {}),
|
|
331
|
+
// Only an explicit `false` from the daemon makes this terminal; an
|
|
332
|
+
// older daemon sends nothing and keeps today's retryable answer.
|
|
333
|
+
retryable: !(body?.retryable === false || isSignInBoundCode(body?.upstreamCode)),
|
|
334
|
+
});
|
|
278
335
|
}
|
|
279
336
|
if (res.status === 503 || body?.code === 'DELEGATED_CLI_CREDENTIAL_UNAVAILABLE') {
|
|
280
337
|
throw new Error(`the daemon's delegated CLI broker is alive but cannot mint a credential ` +
|
|
@@ -139,7 +139,7 @@ export declare function resolveEngine(env?: NodeJS.ProcessEnv): EngineResolution
|
|
|
139
139
|
* Build the engine argv for a `skrr code` invocation.
|
|
140
140
|
*
|
|
141
141
|
* Exported and pure so the forwarding contract is unit-testable without an
|
|
142
|
-
* oclif runtime or a real binary — the convention `
|
|
142
|
+
* oclif runtime or a real binary — the convention `buildDaemonLogsArgs` uses.
|
|
143
143
|
*
|
|
144
144
|
* `subcommand === null` means bare `skrr code`, which launches the interactive
|
|
145
145
|
* TUI: the engine takes no subcommand for that, and passing one would put it
|
|
@@ -266,7 +266,7 @@ function resolveEngine(env = process.env) {
|
|
|
266
266
|
* Build the engine argv for a `skrr code` invocation.
|
|
267
267
|
*
|
|
268
268
|
* Exported and pure so the forwarding contract is unit-testable without an
|
|
269
|
-
* oclif runtime or a real binary — the convention `
|
|
269
|
+
* oclif runtime or a real binary — the convention `buildDaemonLogsArgs` uses.
|
|
270
270
|
*
|
|
271
271
|
* `subcommand === null` means bare `skrr code`, which launches the interactive
|
|
272
272
|
* TUI: the engine takes no subcommand for that, and passing one would put it
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Scope/version coordinates are intent. The server re-authorizes every read and write. */
|
|
2
|
+
export declare const RESPONSIBILITY_CONTROL_SCOPES: readonly ["mute_updates", "pause_observation", "cancel_worker", "cancel_episode", "cancel_subtree", "retire"];
|
|
3
|
+
/**
|
|
4
|
+
* Apply only the saved preview, never a newly fetched version or live target set.
|
|
5
|
+
* Presentation and authority fields cannot become request fields. The canonical
|
|
6
|
+
* service validates every attempt pin and rejects stale or enlarged selections.
|
|
7
|
+
*/
|
|
8
|
+
export declare function controlFromPreview(commitmentId: string, preview: Record<string, unknown>, idempotencyKey: string, reason?: string): Record<string, unknown>;
|
|
9
|
+
export declare function renderControl(value: Record<string, unknown>, log: (line: string) => void): void;
|
|
10
|
+
export declare function renderReadiness(value: Record<string, unknown>, log: (line: string) => void): void;
|
|
11
|
+
export declare function renderProbe(value: Record<string, unknown>, log: (line: string) => void): void;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RESPONSIBILITY_CONTROL_SCOPES = void 0;
|
|
4
|
+
exports.controlFromPreview = controlFromPreview;
|
|
5
|
+
exports.renderControl = renderControl;
|
|
6
|
+
exports.renderReadiness = renderReadiness;
|
|
7
|
+
exports.renderProbe = renderProbe;
|
|
8
|
+
/** Scope/version coordinates are intent. The server re-authorizes every read and write. */
|
|
9
|
+
exports.RESPONSIBILITY_CONTROL_SCOPES = [
|
|
10
|
+
'mute_updates', 'pause_observation', 'cancel_worker', 'cancel_episode', 'cancel_subtree', 'retire',
|
|
11
|
+
];
|
|
12
|
+
function record(value) {
|
|
13
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Apply only the saved preview, never a newly fetched version or live target set.
|
|
17
|
+
* Presentation and authority fields cannot become request fields. The canonical
|
|
18
|
+
* service validates every attempt pin and rejects stale or enlarged selections.
|
|
19
|
+
*/
|
|
20
|
+
function controlFromPreview(commitmentId, preview, idempotencyKey, reason) {
|
|
21
|
+
if (preview.schemaVersion !== 1 || preview.commitmentId !== commitmentId) {
|
|
22
|
+
throw new Error('Use a control preview for this exact Commitment.');
|
|
23
|
+
}
|
|
24
|
+
if (preview.available !== true)
|
|
25
|
+
throw new Error('The reviewed control is unavailable; refresh its preview.');
|
|
26
|
+
if (!exports.RESPONSIBILITY_CONTROL_SCOPES.some((scope) => scope === preview.scope)) {
|
|
27
|
+
throw new Error('The preview has an unsupported control scope.');
|
|
28
|
+
}
|
|
29
|
+
if (typeof preview.expectedVersion !== 'number' || !Number.isSafeInteger(preview.expectedVersion)
|
|
30
|
+
|| preview.expectedVersion < 1)
|
|
31
|
+
throw new Error('The preview must pin its Commitment version.');
|
|
32
|
+
if (!idempotencyKey.trim() || idempotencyKey.length > 200)
|
|
33
|
+
throw new Error('Use an idempotency key of 1..200 characters.');
|
|
34
|
+
if (reason !== undefined && reason.length > 1000)
|
|
35
|
+
throw new Error('The control reason must be at most 1000 characters.');
|
|
36
|
+
if (!Array.isArray(preview.targetIds) || !Array.isArray(preview.expectedTargets)
|
|
37
|
+
|| preview.targetIds.length > 500 || preview.expectedTargets.length > 500) {
|
|
38
|
+
throw new Error('The preview must contain a bounded, frozen target set.');
|
|
39
|
+
}
|
|
40
|
+
const pinKeys = new Set(['id', 'type', 'generation', 'attempt', 'taskWorkflowGeneration',
|
|
41
|
+
'workflowPlanGeneration', 'workflowNodeAttempt', 'workflowTaskRunId', 'bindingDigest',
|
|
42
|
+
'executorAttemptRef', 'taskRunId']);
|
|
43
|
+
const executorKeys = new Set(['kind', 'runId', 'generation', 'attempt']);
|
|
44
|
+
const targetIds = preview.targetIds;
|
|
45
|
+
const pins = preview.expectedTargets;
|
|
46
|
+
if (targetIds.some((id) => typeof id !== 'string' || !id || id.length > 200)
|
|
47
|
+
|| new Set(targetIds).size !== targetIds.length
|
|
48
|
+
|| pins.some((pin) => !record(pin) || typeof pin.id !== 'string'
|
|
49
|
+
|| typeof pin.generation !== 'number' || !Number.isSafeInteger(pin.generation) || pin.generation < 0
|
|
50
|
+
|| Object.keys(pin).some((key) => !pinKeys.has(key))
|
|
51
|
+
|| (pin.executorAttemptRef != null && (!record(pin.executorAttemptRef)
|
|
52
|
+
|| Object.keys(pin.executorAttemptRef).some((key) => !executorKeys.has(key)))))
|
|
53
|
+
|| pins.length !== targetIds.length
|
|
54
|
+
|| new Set(pins.map((pin) => record(pin) ? pin.id : null)).size !== pins.length
|
|
55
|
+
|| pins.some((pin) => !record(pin) || !targetIds.includes(pin.id))) {
|
|
56
|
+
throw new Error('Use the exact target identities and generation pins returned by control preview.');
|
|
57
|
+
}
|
|
58
|
+
if (String(preview.scope).startsWith('cancel_') && pins.length === 0) {
|
|
59
|
+
throw new Error('A cancellation preview must identify the exact worker or episode attempts.');
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
scope: preview.scope, expectedVersion: preview.expectedVersion,
|
|
63
|
+
targetIds, expectedTargets: pins, idempotencyKey,
|
|
64
|
+
...(reason !== undefined ? { reason } : {}),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function renderControl(value, log) {
|
|
68
|
+
log(`${value.scope}: ${value.state ?? (value.available === true ? 'available' : 'unavailable')}`);
|
|
69
|
+
if (value.controlId)
|
|
70
|
+
log(`Control receipt: ${value.controlId}`);
|
|
71
|
+
if (value.expectedVersion !== undefined)
|
|
72
|
+
log(`Commitment version: ${value.expectedVersion}`);
|
|
73
|
+
if (value.unavailableReason)
|
|
74
|
+
log(`Reason: ${value.unavailableReason}`);
|
|
75
|
+
for (const key of ['affected', 'remaining']) {
|
|
76
|
+
const rows = value[key];
|
|
77
|
+
if (!Array.isArray(rows))
|
|
78
|
+
continue;
|
|
79
|
+
log(`${key === 'affected' ? 'Affected' : 'Remaining'} work: ${rows.length}`);
|
|
80
|
+
for (const row of rows)
|
|
81
|
+
if (record(row))
|
|
82
|
+
log(` ${row.id}: ${row.status ?? 'unknown'}${row.acknowledgement ? ` (${row.acknowledgement})` : ''}`);
|
|
83
|
+
}
|
|
84
|
+
if (Array.isArray(value.warnings))
|
|
85
|
+
for (const warning of value.warnings)
|
|
86
|
+
log(String(warning));
|
|
87
|
+
}
|
|
88
|
+
function renderReadiness(value, log) {
|
|
89
|
+
const activation = value.object === 'commitment_readiness_activation_receipt';
|
|
90
|
+
log(`${activation ? 'Activation' : 'Readiness'} receipt: ${value.id ?? 'not recorded'}`);
|
|
91
|
+
if (activation) {
|
|
92
|
+
log(`Validated version: ${value.validatedVersion ?? 'unknown'}`);
|
|
93
|
+
log(`Activated version: ${value.activatedVersion ?? 'unknown'}`);
|
|
94
|
+
if (record(value.materialization))
|
|
95
|
+
log(`Trigger materialization: ${value.materialization.status ?? 'unknown'}`);
|
|
96
|
+
log(`Worker observation: ${value.workerObservationConfirmed === true ? 'confirmed' : 'unconfirmed'}`);
|
|
97
|
+
}
|
|
98
|
+
if (record(value.preflight))
|
|
99
|
+
log(`Preflight: ${value.preflight.verdict ?? 'unknown'}`);
|
|
100
|
+
if (record(value.observation)) {
|
|
101
|
+
log(`Observation: ${value.observation.status ?? 'unknown'}`);
|
|
102
|
+
log(`Required source coverage: ${value.observation.requiredCoverageComplete === true ? 'complete' : 'incomplete'}`);
|
|
103
|
+
log(`Worker observation: ${value.observation.workerObservationConfirmed === true ? 'confirmed' : 'unconfirmed'}`);
|
|
104
|
+
}
|
|
105
|
+
if (value.isCurrent !== undefined)
|
|
106
|
+
log(`Current evidence: ${value.isCurrent === true ? 'yes' : 'no'}`);
|
|
107
|
+
if (value.validUntil)
|
|
108
|
+
log(`Evidence valid until: ${value.validUntil}`);
|
|
109
|
+
}
|
|
110
|
+
function renderProbe(value, log) {
|
|
111
|
+
log(`Source observation: ${value.status ?? 'unknown'}`);
|
|
112
|
+
log(`Coverage: ${value.coverageComplete === true ? 'complete' : 'incomplete'}`);
|
|
113
|
+
if (Array.isArray(value.rows))
|
|
114
|
+
for (const row of value.rows) {
|
|
115
|
+
if (record(row))
|
|
116
|
+
log(` ${row.source ?? row.key ?? 'source'}: ${row.status ?? 'unknown'}${row.reason ? ` (${row.reason})` : ''}`);
|
|
117
|
+
}
|
|
118
|
+
log(`Worker observation: ${value.workerObservationConfirmed === true ? 'confirmed' : 'unconfirmed'}`);
|
|
119
|
+
}
|