snipara-companion 3.2.28 → 3.2.29
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/CHANGELOG.md +11 -0
- package/README.md +5 -1
- package/dist/index.d.ts +44 -0
- package/dist/index.js +176 -4
- package/docs/FULL_REFERENCE.md +5 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
Release notes for `snipara-companion`, newest first.
|
|
4
4
|
|
|
5
|
+
## New In 3.2.29
|
|
6
|
+
|
|
7
|
+
- Extends `workflow producer-report` with attributed gated-execution samples
|
|
8
|
+
joined to their persisted supervisor reviews.
|
|
9
|
+
- Adds per-`workerId`/`workCategory` `workerTrust` breakdowns with execution,
|
|
10
|
+
review, accepted, blocked, incomplete receipt-family, and workflow fingerprint
|
|
11
|
+
counts.
|
|
12
|
+
- Keeps the read model deliberately supervised: every pair remains
|
|
13
|
+
`probation_supervised`, `hardGateReady=false`, and lists the evidence still
|
|
14
|
+
required before the separate Trust Promotion gates can be implemented.
|
|
15
|
+
|
|
5
16
|
## New In 3.2.28
|
|
6
17
|
|
|
7
18
|
- Adds bounded Outcome Loop retrieval correlation to generated Codex, Claude,
|
package/README.md
CHANGED
|
@@ -257,7 +257,11 @@ redacted Coding Intelligence Ledger, not automatic durable memory, worker
|
|
|
257
257
|
execution, calibrated confidence, or server-side attestation. Use
|
|
258
258
|
`workflow producer-report` to inspect local adoption, reason-code counts, sample
|
|
259
259
|
size, reviewed/rejected/unreviewed counts, invalid artifacts, and calibration
|
|
260
|
-
caveats before any future hard gate.
|
|
260
|
+
caveats before any future hard gate. The report also joins attributed gated
|
|
261
|
+
receipts from `.snipara/orchestrator/executions/` with persisted supervisor
|
|
262
|
+
reviews, then emits `workerReceipts` and a per-`workerId`/`workCategory`
|
|
263
|
+
`workerTrust` breakdown. This is observability only: every pair remains
|
|
264
|
+
`probation_supervised` and `hardGateReady=false`.
|
|
261
265
|
The report also recognizes exported PR Answer Pack decision-capture artifacts
|
|
262
266
|
with producer kind `pr_answer_pack_decision_capture`, so calibration can track
|
|
263
267
|
more than the workflow producer once those artifacts are present locally.
|
package/dist/index.d.ts
CHANGED
|
@@ -3543,6 +3543,39 @@ interface ProducerLoopArtifactReportSummary {
|
|
|
3543
3543
|
reviewedAt?: string;
|
|
3544
3544
|
reviewer?: string;
|
|
3545
3545
|
}
|
|
3546
|
+
interface WorkerExecutionReceiptReportSummary {
|
|
3547
|
+
receiptId: string;
|
|
3548
|
+
schemaVersion: string;
|
|
3549
|
+
recordedAt?: string;
|
|
3550
|
+
workerId: string;
|
|
3551
|
+
workCategory: string;
|
|
3552
|
+
routingCardRef?: string;
|
|
3553
|
+
workflowFingerprint?: string;
|
|
3554
|
+
executionActor: string;
|
|
3555
|
+
status?: string;
|
|
3556
|
+
reviewStatus: "review_pending" | "accepted" | "blocked";
|
|
3557
|
+
executed: boolean;
|
|
3558
|
+
receiptFamilyComplete: boolean;
|
|
3559
|
+
missingReceiptFamilies: string[];
|
|
3560
|
+
path: string;
|
|
3561
|
+
relativePath: string;
|
|
3562
|
+
reviewPath?: string;
|
|
3563
|
+
reviewRelativePath?: string;
|
|
3564
|
+
}
|
|
3565
|
+
interface WorkerTrustReportRow {
|
|
3566
|
+
workerId: string;
|
|
3567
|
+
workCategory: string;
|
|
3568
|
+
state: "probation_supervised";
|
|
3569
|
+
sampleSize: number;
|
|
3570
|
+
executedSampleSize: number;
|
|
3571
|
+
reviewedSampleSize: number;
|
|
3572
|
+
verifiedSampleSize: number;
|
|
3573
|
+
blockedSampleSize: number;
|
|
3574
|
+
incompleteReceiptSampleSize: number;
|
|
3575
|
+
workflowFingerprints: string[];
|
|
3576
|
+
hardGateReady: false;
|
|
3577
|
+
nextRequired: string[];
|
|
3578
|
+
}
|
|
3546
3579
|
interface ProducerLoopReport {
|
|
3547
3580
|
version: typeof PRODUCER_LOOP_REPORT_VERSION;
|
|
3548
3581
|
generatedAt: string;
|
|
@@ -3566,6 +3599,17 @@ interface ProducerLoopReport {
|
|
|
3566
3599
|
reasonCodes: {
|
|
3567
3600
|
counts: Record<string, number>;
|
|
3568
3601
|
};
|
|
3602
|
+
workerReceipts: {
|
|
3603
|
+
sourceDirectories: string[];
|
|
3604
|
+
sampleSize: number;
|
|
3605
|
+
samples: WorkerExecutionReceiptReportSummary[];
|
|
3606
|
+
invalidArtifacts: Array<{
|
|
3607
|
+
path: string;
|
|
3608
|
+
relativePath: string;
|
|
3609
|
+
error: string;
|
|
3610
|
+
}>;
|
|
3611
|
+
};
|
|
3612
|
+
workerTrust: WorkerTrustReportRow[];
|
|
3569
3613
|
calibration: {
|
|
3570
3614
|
status: "no_samples" | "insufficient_samples" | "reviewable_sample_set";
|
|
3571
3615
|
sampleSize: number;
|
package/dist/index.js
CHANGED
|
@@ -21549,6 +21549,154 @@ function producerLoopCalibrationStatus(sampleSize, reviewedSampleSize, minReview
|
|
|
21549
21549
|
}
|
|
21550
21550
|
return "reviewable_sample_set";
|
|
21551
21551
|
}
|
|
21552
|
+
var WORKER_RECEIPT_RELATIVE_DIR = path22.join(".snipara", "orchestrator", "executions");
|
|
21553
|
+
var WORKER_REVIEW_RELATIVE_DIR = path22.join(".snipara", "orchestrator", "reviews");
|
|
21554
|
+
var REQUIRED_WORKER_RECEIPT_FAMILIES = [
|
|
21555
|
+
"handoffReceiptId",
|
|
21556
|
+
"claimId",
|
|
21557
|
+
"proofReceiptIds",
|
|
21558
|
+
"outcomeReceiptId",
|
|
21559
|
+
"brainUpdateReceiptId"
|
|
21560
|
+
];
|
|
21561
|
+
function listJsonFiles(relativeDir, cwd) {
|
|
21562
|
+
const directory = path22.join(cwd, relativeDir);
|
|
21563
|
+
if (!fs23.existsSync(directory)) {
|
|
21564
|
+
return [];
|
|
21565
|
+
}
|
|
21566
|
+
return fs23.readdirSync(directory, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => path22.join(directory, entry.name)).sort();
|
|
21567
|
+
}
|
|
21568
|
+
function readWorkerReviewFiles(cwd) {
|
|
21569
|
+
const reviews = /* @__PURE__ */ new Map();
|
|
21570
|
+
const invalid = [];
|
|
21571
|
+
for (const filePath of listJsonFiles(WORKER_REVIEW_RELATIVE_DIR, cwd)) {
|
|
21572
|
+
try {
|
|
21573
|
+
const parsed = JSON.parse(fs23.readFileSync(filePath, "utf-8"));
|
|
21574
|
+
if (!isRecord10(parsed)) {
|
|
21575
|
+
throw new Error("review must be a JSON object");
|
|
21576
|
+
}
|
|
21577
|
+
const receiptId = stringValue7(parsed.receiptId);
|
|
21578
|
+
if (!receiptId) {
|
|
21579
|
+
throw new Error("review is missing receiptId");
|
|
21580
|
+
}
|
|
21581
|
+
reviews.set(receiptId, { value: parsed, path: filePath });
|
|
21582
|
+
} catch (error) {
|
|
21583
|
+
invalid.push({
|
|
21584
|
+
path: filePath,
|
|
21585
|
+
relativePath: toProjectRelativePath3(filePath, cwd),
|
|
21586
|
+
error: error instanceof Error ? error.message : String(error)
|
|
21587
|
+
});
|
|
21588
|
+
}
|
|
21589
|
+
}
|
|
21590
|
+
return { reviews, invalid };
|
|
21591
|
+
}
|
|
21592
|
+
function missingWorkerReceiptFamilies(receipt) {
|
|
21593
|
+
const refs = isRecord10(receipt.receiptRefs) ? receipt.receiptRefs : isRecord10(receipt.receipt) ? receipt.receipt : {};
|
|
21594
|
+
return REQUIRED_WORKER_RECEIPT_FAMILIES.filter((family) => {
|
|
21595
|
+
const value = refs[family];
|
|
21596
|
+
return family === "proofReceiptIds" ? !Array.isArray(value) || value.filter((item) => Boolean(stringValue7(item))).length === 0 : !stringValue7(value);
|
|
21597
|
+
});
|
|
21598
|
+
}
|
|
21599
|
+
function summarizeWorkerExecutionReceipts(cwd) {
|
|
21600
|
+
const reviewFiles = readWorkerReviewFiles(cwd);
|
|
21601
|
+
const invalid = [...reviewFiles.invalid];
|
|
21602
|
+
const samples = [];
|
|
21603
|
+
for (const filePath of listJsonFiles(WORKER_RECEIPT_RELATIVE_DIR, cwd)) {
|
|
21604
|
+
try {
|
|
21605
|
+
const parsed = JSON.parse(fs23.readFileSync(filePath, "utf-8"));
|
|
21606
|
+
if (!isRecord10(parsed)) {
|
|
21607
|
+
throw new Error("receipt must be a JSON object");
|
|
21608
|
+
}
|
|
21609
|
+
const receiptId = stringValue7(parsed.receiptId);
|
|
21610
|
+
const schemaVersion = stringValue7(parsed.schemaVersion);
|
|
21611
|
+
if (!receiptId || !schemaVersion) {
|
|
21612
|
+
throw new Error("receipt is missing receiptId or schemaVersion");
|
|
21613
|
+
}
|
|
21614
|
+
const attribution = isRecord10(parsed.workerAttribution) ? parsed.workerAttribution : {};
|
|
21615
|
+
const gate2 = isRecord10(parsed.gate) ? parsed.gate : {};
|
|
21616
|
+
const review = reviewFiles.reviews.get(receiptId);
|
|
21617
|
+
const reviewStatusValue = stringValue7(review?.value.reviewStatus);
|
|
21618
|
+
const reviewStatus = reviewStatusValue === "accepted" ? "accepted" : reviewStatusValue === "blocked" ? "blocked" : "review_pending";
|
|
21619
|
+
const workerId = stringValue7(parsed.workerId) ?? stringValue7(attribution.workerId) ?? stringValue7(parsed.selectedWorkerCandidateId) ?? stringValue7(gate2.selectedWorkerCandidateId) ?? "main_agent";
|
|
21620
|
+
const workCategory = stringValue7(parsed.workCategory) ?? stringValue7(attribution.workCategory) ?? "unknown";
|
|
21621
|
+
const executionActor = stringValue7(parsed.executionActor) ?? stringValue7(attribution.executionActor) ?? (Number(parsed.workersSpawned ?? 0) > 0 ? "worker" : "main_agent");
|
|
21622
|
+
const missingReceiptFamilies = missingWorkerReceiptFamilies(parsed);
|
|
21623
|
+
samples.push({
|
|
21624
|
+
receiptId,
|
|
21625
|
+
schemaVersion,
|
|
21626
|
+
recordedAt: stringValue7(parsed.recordedAt),
|
|
21627
|
+
workerId,
|
|
21628
|
+
workCategory,
|
|
21629
|
+
routingCardRef: stringValue7(parsed.routingCardRef) ?? stringValue7(attribution.routingCardRef),
|
|
21630
|
+
workflowFingerprint: stringValue7(parsed.workflowFingerprint) ?? stringValue7(attribution.workflowFingerprint),
|
|
21631
|
+
executionActor,
|
|
21632
|
+
status: stringValue7(parsed.status),
|
|
21633
|
+
reviewStatus,
|
|
21634
|
+
executed: executionActor === "worker" || Number(parsed.workersSpawned ?? 0) > 0,
|
|
21635
|
+
receiptFamilyComplete: missingReceiptFamilies.length === 0,
|
|
21636
|
+
missingReceiptFamilies,
|
|
21637
|
+
path: filePath,
|
|
21638
|
+
relativePath: toProjectRelativePath3(filePath, cwd),
|
|
21639
|
+
...review ? {
|
|
21640
|
+
reviewPath: review.path,
|
|
21641
|
+
reviewRelativePath: toProjectRelativePath3(review.path, cwd)
|
|
21642
|
+
} : {}
|
|
21643
|
+
});
|
|
21644
|
+
} catch (error) {
|
|
21645
|
+
invalid.push({
|
|
21646
|
+
path: filePath,
|
|
21647
|
+
relativePath: toProjectRelativePath3(filePath, cwd),
|
|
21648
|
+
error: error instanceof Error ? error.message : String(error)
|
|
21649
|
+
});
|
|
21650
|
+
}
|
|
21651
|
+
}
|
|
21652
|
+
samples.sort(
|
|
21653
|
+
(left, right) => (left.recordedAt ?? left.receiptId).localeCompare(right.recordedAt ?? right.receiptId)
|
|
21654
|
+
);
|
|
21655
|
+
return { samples, invalid };
|
|
21656
|
+
}
|
|
21657
|
+
function buildWorkerTrustRows(samples, minReviewSampleSize) {
|
|
21658
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
21659
|
+
for (const sample of samples) {
|
|
21660
|
+
const key = `${sample.workerId}\0${sample.workCategory}`;
|
|
21661
|
+
grouped.set(key, [...grouped.get(key) ?? [], sample]);
|
|
21662
|
+
}
|
|
21663
|
+
return [...grouped.values()].map((group) => {
|
|
21664
|
+
const reviewedSampleSize = group.filter(
|
|
21665
|
+
(sample) => sample.reviewStatus !== "review_pending"
|
|
21666
|
+
).length;
|
|
21667
|
+
const verifiedSampleSize = group.filter(
|
|
21668
|
+
(sample) => sample.reviewStatus === "accepted"
|
|
21669
|
+
).length;
|
|
21670
|
+
const incompleteReceiptSampleSize = group.filter(
|
|
21671
|
+
(sample) => !sample.receiptFamilyComplete
|
|
21672
|
+
).length;
|
|
21673
|
+
const nextRequired = [
|
|
21674
|
+
reviewedSampleSize < minReviewSampleSize ? `${minReviewSampleSize - reviewedSampleSize} more supervised review sample(s)` : void 0,
|
|
21675
|
+
verifiedSampleSize < 3 ? `${3 - verifiedSampleSize} more accepted verified sample(s)` : void 0,
|
|
21676
|
+
incompleteReceiptSampleSize > 0 ? `${incompleteReceiptSampleSize} sample(s) need complete receipt families` : void 0,
|
|
21677
|
+
group[0].workCategory === "unknown" ? "classify legacy samples by work category" : void 0,
|
|
21678
|
+
"Trust Promotion gate implementation remains required"
|
|
21679
|
+
].filter((item) => Boolean(item));
|
|
21680
|
+
return {
|
|
21681
|
+
workerId: group[0].workerId,
|
|
21682
|
+
workCategory: group[0].workCategory,
|
|
21683
|
+
state: "probation_supervised",
|
|
21684
|
+
sampleSize: group.length,
|
|
21685
|
+
executedSampleSize: group.filter((sample) => sample.executed).length,
|
|
21686
|
+
reviewedSampleSize,
|
|
21687
|
+
verifiedSampleSize,
|
|
21688
|
+
blockedSampleSize: group.filter((sample) => sample.reviewStatus === "blocked").length,
|
|
21689
|
+
incompleteReceiptSampleSize,
|
|
21690
|
+
workflowFingerprints: uniqueStrings18(
|
|
21691
|
+
group.map((sample) => sample.workflowFingerprint).filter((value) => Boolean(value))
|
|
21692
|
+
),
|
|
21693
|
+
hardGateReady: false,
|
|
21694
|
+
nextRequired
|
|
21695
|
+
};
|
|
21696
|
+
}).sort(
|
|
21697
|
+
(left, right) => left.workerId === right.workerId ? left.workCategory.localeCompare(right.workCategory) : left.workerId.localeCompare(right.workerId)
|
|
21698
|
+
);
|
|
21699
|
+
}
|
|
21552
21700
|
function buildProducerLoopReport(options = {}) {
|
|
21553
21701
|
const cwd = path22.resolve(options.cwd ?? process.cwd());
|
|
21554
21702
|
const minReviewSampleSize = Math.max(
|
|
@@ -21589,12 +21737,16 @@ function buildProducerLoopReport(options = {}) {
|
|
|
21589
21737
|
reviewedSampleSize,
|
|
21590
21738
|
minReviewSampleSize
|
|
21591
21739
|
);
|
|
21740
|
+
const workerReceiptReport = summarizeWorkerExecutionReceipts(cwd);
|
|
21741
|
+
const workerTrust = buildWorkerTrustRows(workerReceiptReport.samples, minReviewSampleSize);
|
|
21592
21742
|
const recommendedActions = [
|
|
21593
21743
|
artifacts.length === 0 ? "Run a current Producer Loop producer, such as workflow phase-commit/final-commit or PR Answer Pack decision capture, to create samples." : void 0,
|
|
21594
21744
|
artifacts.length > 0 && reviewedSampleSize < minReviewSampleSize ? `Review at least ${minReviewSampleSize} local Producer Loop samples before considering any calibration signal.` : void 0,
|
|
21595
21745
|
unreviewedSampleSize > 0 ? "Mark local samples with workflow producer-review after checking the embedded evidence." : void 0,
|
|
21596
21746
|
rejectedSampleSize > 0 ? "Inspect rejected Producer Loop samples for false positives, missing context, or reason-code drift." : void 0,
|
|
21597
21747
|
invalidArtifacts.length > 0 ? "Inspect invalid Producer Loop artifacts before trusting adoption counts." : void 0,
|
|
21748
|
+
workerReceiptReport.samples.length === 0 ? "Run agents execute-gated and persist agents review-gated results to create worker-attributed receipt samples." : void 0,
|
|
21749
|
+
workerReceiptReport.invalid.length > 0 ? "Inspect invalid worker execution receipts or reviews before trusting per-worker counts." : void 0,
|
|
21598
21750
|
"Review false positives, missing outcomes, and reason-code drift before any future enforcement."
|
|
21599
21751
|
].filter((item) => Boolean(item));
|
|
21600
21752
|
return {
|
|
@@ -21616,6 +21768,13 @@ function buildProducerLoopReport(options = {}) {
|
|
|
21616
21768
|
reasonCodes: {
|
|
21617
21769
|
counts: reasonCodeCounts
|
|
21618
21770
|
},
|
|
21771
|
+
workerReceipts: {
|
|
21772
|
+
sourceDirectories: [WORKER_RECEIPT_RELATIVE_DIR, WORKER_REVIEW_RELATIVE_DIR],
|
|
21773
|
+
sampleSize: workerReceiptReport.samples.length,
|
|
21774
|
+
samples: workerReceiptReport.samples,
|
|
21775
|
+
invalidArtifacts: workerReceiptReport.invalid
|
|
21776
|
+
},
|
|
21777
|
+
workerTrust,
|
|
21619
21778
|
calibration: {
|
|
21620
21779
|
status: calibrationStatus,
|
|
21621
21780
|
sampleSize: artifacts.length,
|
|
@@ -21815,6 +21974,19 @@ function printProducerLoopReport(report) {
|
|
|
21815
21974
|
if (report.invalidArtifacts.length > 0) {
|
|
21816
21975
|
printKeyValue2("Invalid artifacts:", report.invalidArtifacts.length);
|
|
21817
21976
|
}
|
|
21977
|
+
printKeyValue2("Worker receipts:", report.workerReceipts.sampleSize);
|
|
21978
|
+
if (report.workerReceipts.invalidArtifacts.length > 0) {
|
|
21979
|
+
printKeyValue2("Invalid worker evidence:", report.workerReceipts.invalidArtifacts.length);
|
|
21980
|
+
}
|
|
21981
|
+
if (report.workerTrust.length > 0) {
|
|
21982
|
+
console.log("");
|
|
21983
|
+
console.log(import_chalk6.default.bold("Worker Trust (supervised probation)"));
|
|
21984
|
+
for (const row of report.workerTrust) {
|
|
21985
|
+
console.log(
|
|
21986
|
+
`- ${row.workerId} / ${row.workCategory}: ${row.verifiedSampleSize} accepted, ${row.blockedSampleSize} blocked, ${row.reviewedSampleSize}/${row.sampleSize} reviewed`
|
|
21987
|
+
);
|
|
21988
|
+
}
|
|
21989
|
+
}
|
|
21818
21990
|
if (report.recommendedActions.length > 0) {
|
|
21819
21991
|
console.log("");
|
|
21820
21992
|
console.log(import_chalk6.default.bold("Recommended Actions"));
|
|
@@ -33407,7 +33579,7 @@ function applyLocalContextMutationPlan(options) {
|
|
|
33407
33579
|
writtenFiles
|
|
33408
33580
|
};
|
|
33409
33581
|
}
|
|
33410
|
-
function
|
|
33582
|
+
function listJsonFiles2(dir) {
|
|
33411
33583
|
if (!fs30.existsSync(dir)) return [];
|
|
33412
33584
|
return fs30.readdirSync(dir).filter((name) => name.endsWith(".json")).map((name) => path30.join(dir, name)).sort((left, right) => left.localeCompare(right));
|
|
33413
33585
|
}
|
|
@@ -33534,7 +33706,7 @@ function collectWorkflowDriftSignals(cwd) {
|
|
|
33534
33706
|
}
|
|
33535
33707
|
function collectDecisionDriftSignals(cwd) {
|
|
33536
33708
|
const pendingDir = path30.join(cwd, ".snipara", "decisions", "pending");
|
|
33537
|
-
const pending =
|
|
33709
|
+
const pending = listJsonFiles2(pendingDir).map((file) => toProjectRelativePath4(file, cwd));
|
|
33538
33710
|
return [
|
|
33539
33711
|
pending.length > 0 ? {
|
|
33540
33712
|
id: "decision-requests-pending",
|
|
@@ -33558,8 +33730,8 @@ function collectDecisionDriftSignals(cwd) {
|
|
|
33558
33730
|
];
|
|
33559
33731
|
}
|
|
33560
33732
|
function collectContextControlDriftSignals(cwd) {
|
|
33561
|
-
const planFiles =
|
|
33562
|
-
const receiptFiles =
|
|
33733
|
+
const planFiles = listJsonFiles2(path30.join(cwd, CONTEXT_CONTROL_PLANS_RELATIVE_DIR));
|
|
33734
|
+
const receiptFiles = listJsonFiles2(path30.join(cwd, CONTEXT_CONTROL_APPLIED_RELATIVE_DIR));
|
|
33563
33735
|
const receiptPlanHashes = new Set(
|
|
33564
33736
|
receiptFiles.map((file) => readJsonFileSafe(file).value).filter(isContextMutationApplyReceipt).map((receipt) => receipt.planHash)
|
|
33565
33737
|
);
|
package/docs/FULL_REFERENCE.md
CHANGED
|
@@ -324,7 +324,11 @@ snipara-companion workflow resume --include-session-context
|
|
|
324
324
|
workflow phase/final commits or exported PR Answer Pack decision-capture
|
|
325
325
|
producers, then reports adoption, producer kinds, workflow ids, reason-code
|
|
326
326
|
counts, invalid artifacts, sample size, reviewed/rejected/unreviewed counts,
|
|
327
|
-
and calibration caveats with `hardGateReady=false`.
|
|
327
|
+
and calibration caveats with `hardGateReady=false`. It also scans attributed
|
|
328
|
+
gated receipts and persisted reviews under `.snipara/orchestrator/`, reports
|
|
329
|
+
receipt-family completeness, and groups supervised evidence by
|
|
330
|
+
`(workerId, workCategory)` in `workerTrust`. It never promotes a worker or
|
|
331
|
+
changes an execution gate.
|
|
328
332
|
- `workflow producer-review` marks one local Producer Loop artifact as
|
|
329
333
|
`sample_reviewed` or `sample_rejected` after operator review. Use
|
|
330
334
|
`--artifact <path|file|artifactId>` for an exact sample or `--latest` for the
|