snipara-companion 3.2.28 → 3.2.30

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 CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  Release notes for `snipara-companion`, newest first.
4
4
 
5
+ ## New In 3.2.30
6
+
7
+ - Persists an explicit `--ack-review-only` as a 15-minute, one-use
8
+ acknowledgement bound to the exact guard profile, action, and finding
9
+ fingerprint, so the immediately following Git hook can consume it safely.
10
+ - Invalidates that acknowledgement when findings change and never applies it
11
+ to required acknowledgements, active conflicts, or hard blocks.
12
+ - Resolves collaboration state from `SNIPARA_WORKSPACE_DIR` or the Git
13
+ top-level directory, preventing filtered package commands from writing a
14
+ nested `packages/cli/.snipara/` session.
15
+
16
+ ## New In 3.2.29
17
+
18
+ - Extends `workflow producer-report` with attributed gated-execution samples
19
+ joined to their persisted supervisor reviews.
20
+ - Adds per-`workerId`/`workCategory` `workerTrust` breakdowns with execution,
21
+ review, accepted, blocked, incomplete receipt-family, and workflow fingerprint
22
+ counts.
23
+ - Keeps the read model deliberately supervised: every pair remains
24
+ `probation_supervised`, `hardGateReady=false`, and lists the evidence still
25
+ required before the separate Trust Promotion gates can be implemented.
26
+
5
27
  ## New In 3.2.28
6
28
 
7
29
  - 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
@@ -2831,6 +2831,7 @@ declare function buildCodingIntelligenceLedger(options?: CodingLedgerBuildOption
2831
2831
  declare function codingLedgerExportCommand(options: CodingLedgerExportCommandOptions): Promise<void>;
2832
2832
 
2833
2833
  declare const COLLABORATION_STATE_RELATIVE_PATH: string;
2834
+ type CollaborationGuardProfile = "edit" | "pre-commit" | "pre-push" | "pre-deploy" | "migration" | "schema" | "release-package";
2834
2835
  type CollaborationGuardActionKind = "safe_to_ack" | "needs_handoff" | "needs_test" | "needs_package_review" | "blocking_conflict" | "guard_unavailable";
2835
2836
  interface CollaborationGuardActionCard {
2836
2837
  kind: CollaborationGuardActionKind;
@@ -2910,6 +2911,13 @@ interface CollaborationLocalLeaseRecord {
2910
2911
  expiresAt?: string | null;
2911
2912
  workSessionId?: string;
2912
2913
  }
2914
+ interface CollaborationReviewOnlyAcknowledgement {
2915
+ profile: CollaborationGuardProfile;
2916
+ action: string;
2917
+ findingFingerprint: string;
2918
+ acknowledgedAt: string;
2919
+ expiresAt: string;
2920
+ }
2913
2921
  interface CollaborationLocalState {
2914
2922
  schemaVersion: "snipara.collaboration.v1";
2915
2923
  updatedAt: string;
@@ -2926,6 +2934,7 @@ interface CollaborationLocalState {
2926
2934
  files: string[];
2927
2935
  resources: CollaborationResource[];
2928
2936
  leases: CollaborationLocalLeaseRecord[];
2937
+ reviewOnlyAcknowledgements: CollaborationReviewOnlyAcknowledgement[];
2929
2938
  lastGuard?: {
2930
2939
  decision: CollaborationGuardDecision;
2931
2940
  severity: string;
@@ -3543,6 +3552,39 @@ interface ProducerLoopArtifactReportSummary {
3543
3552
  reviewedAt?: string;
3544
3553
  reviewer?: string;
3545
3554
  }
3555
+ interface WorkerExecutionReceiptReportSummary {
3556
+ receiptId: string;
3557
+ schemaVersion: string;
3558
+ recordedAt?: string;
3559
+ workerId: string;
3560
+ workCategory: string;
3561
+ routingCardRef?: string;
3562
+ workflowFingerprint?: string;
3563
+ executionActor: string;
3564
+ status?: string;
3565
+ reviewStatus: "review_pending" | "accepted" | "blocked";
3566
+ executed: boolean;
3567
+ receiptFamilyComplete: boolean;
3568
+ missingReceiptFamilies: string[];
3569
+ path: string;
3570
+ relativePath: string;
3571
+ reviewPath?: string;
3572
+ reviewRelativePath?: string;
3573
+ }
3574
+ interface WorkerTrustReportRow {
3575
+ workerId: string;
3576
+ workCategory: string;
3577
+ state: "probation_supervised";
3578
+ sampleSize: number;
3579
+ executedSampleSize: number;
3580
+ reviewedSampleSize: number;
3581
+ verifiedSampleSize: number;
3582
+ blockedSampleSize: number;
3583
+ incompleteReceiptSampleSize: number;
3584
+ workflowFingerprints: string[];
3585
+ hardGateReady: false;
3586
+ nextRequired: string[];
3587
+ }
3546
3588
  interface ProducerLoopReport {
3547
3589
  version: typeof PRODUCER_LOOP_REPORT_VERSION;
3548
3590
  generatedAt: string;
@@ -3566,6 +3608,17 @@ interface ProducerLoopReport {
3566
3608
  reasonCodes: {
3567
3609
  counts: Record<string, number>;
3568
3610
  };
3611
+ workerReceipts: {
3612
+ sourceDirectories: string[];
3613
+ sampleSize: number;
3614
+ samples: WorkerExecutionReceiptReportSummary[];
3615
+ invalidArtifacts: Array<{
3616
+ path: string;
3617
+ relativePath: string;
3618
+ error: string;
3619
+ }>;
3620
+ };
3621
+ workerTrust: WorkerTrustReportRow[];
3569
3622
  calibration: {
3570
3623
  status: "no_samples" | "insufficient_samples" | "reviewable_sample_set";
3571
3624
  sampleSize: number;
package/dist/index.js CHANGED
@@ -15400,6 +15400,7 @@ var DEFAULT_LOCAL_CODE_MAX_FILES = 2e3;
15400
15400
  var HOSTED_GUARD_MAX_FILES = 450;
15401
15401
  var HOSTED_GUARD_MAX_RESOURCES = 850;
15402
15402
  var HOSTED_GUARD_MAX_SYMBOL_RESOURCES = 160;
15403
+ var REVIEW_ONLY_ACK_TTL_MS = 15 * 60 * 1e3;
15403
15404
  var HOOK_BLOCK_PREFIX2 = "snipara:collaboration-guard";
15404
15405
  var CHECKOUT_GIT_WRITE_RESOURCE = {
15405
15406
  kind: "SURFACE",
@@ -15460,7 +15461,8 @@ function createEmptyCollaborationState(now = /* @__PURE__ */ new Date()) {
15460
15461
  updatedAt: now.toISOString(),
15461
15462
  files: [],
15462
15463
  resources: [],
15463
- leases: []
15464
+ leases: [],
15465
+ reviewOnlyAcknowledgements: []
15464
15466
  };
15465
15467
  }
15466
15468
  function getCollaborationStatePath(rootDir = process.cwd()) {
@@ -15493,13 +15495,18 @@ function loadCollaborationState(rootDir = process.cwd()) {
15493
15495
  files: normalizeFiles(parsed.files),
15494
15496
  resources: normalizeResources(parsed.resources),
15495
15497
  leases: normalizeLocalLeases(parsed.leases),
15498
+ reviewOnlyAcknowledgements: normalizeReviewOnlyAcknowledgements(
15499
+ parsed.reviewOnlyAcknowledgements
15500
+ ),
15496
15501
  lastGuard: normalizeLastGuard(parsed.lastGuard)
15497
15502
  };
15498
15503
  expireLocalLeases(state);
15504
+ expireReviewOnlyAcknowledgements(state);
15499
15505
  return state;
15500
15506
  }
15501
15507
  function saveCollaborationState(state, rootDir = process.cwd()) {
15502
15508
  expireLocalLeases(state);
15509
+ expireReviewOnlyAcknowledgements(state);
15503
15510
  const statePath = getCollaborationStatePath(rootDir);
15504
15511
  fs20.mkdirSync(path19.dirname(statePath), { recursive: true });
15505
15512
  const tempPath = `${statePath}.${process.pid}.${Date.now()}.tmp`;
@@ -15814,13 +15821,14 @@ async function collaborationGuardCommand(options) {
15814
15821
  const context = resolveCollaborationContext(options);
15815
15822
  const state = loadCollaborationState(context.rootDir);
15816
15823
  const profile = normalizeGuardProfile(options.profile) ?? "edit";
15824
+ const action = normalizeOptionalString3(options.action) ?? profile;
15817
15825
  const files = resolveGuardFiles(context, options, profile);
15818
15826
  const resources = resolveCommandResources(context, files, options, profile);
15819
15827
  ensureFilesOrResources(files, resources, "guard");
15820
15828
  const hostedPayload = buildHostedGuardPayload(files, resources);
15821
15829
  const hosted = await maybeEvaluateHostedGuard(context, {
15822
15830
  workSessionId: options.workSessionId ?? state.workSessionId,
15823
- action: normalizeOptionalString3(options.action) ?? profile,
15831
+ action,
15824
15832
  files: hostedPayload.files,
15825
15833
  resources: hostedPayload.resources,
15826
15834
  persist: options.persist !== false
@@ -15832,26 +15840,39 @@ async function collaborationGuardCommand(options) {
15832
15840
  decision: evaluation.decision,
15833
15841
  severity: evaluation.severity,
15834
15842
  checkedAt: evaluation.evaluatedAt,
15835
- action: normalizeOptionalString3(options.action) ?? profile,
15843
+ action,
15836
15844
  resources: evaluation.resources,
15837
15845
  conflictCount: evaluation.conflicts.length
15838
15846
  };
15839
15847
  state.files = mergeStrings(state.files, files);
15840
15848
  state.resources = mergeResources(state.resources, evaluation.resources);
15849
+ }
15850
+ const explicitReviewOnlyAck = Boolean(options.enforce) && Boolean(options.ackReviewOnly) && isReviewOnlyGuardEvaluation(evaluation);
15851
+ const findingFingerprint = evaluation && isReviewOnlyGuardEvaluation(evaluation) ? fingerprintReviewOnlyGuardEvaluation(evaluation) : void 0;
15852
+ const persistedReviewOnlyAck = Boolean(options.enforce) && !options.ackReviewOnly && Boolean(
15853
+ findingFingerprint && consumeReviewOnlyAcknowledgement(state, profile, action, findingFingerprint)
15854
+ );
15855
+ if (explicitReviewOnlyAck && findingFingerprint) {
15856
+ persistReviewOnlyAcknowledgement(state, profile, action, findingFingerprint);
15857
+ } else if (evaluation && !persistedReviewOnlyAck) {
15858
+ invalidateReviewOnlyAcknowledgements(state, profile, action);
15859
+ }
15860
+ const reviewOnlyAcknowledged = explicitReviewOnlyAck || persistedReviewOnlyAck;
15861
+ const ackSource = explicitReviewOnlyAck ? "explicit" : persistedReviewOnlyAck ? "persisted" : "none";
15862
+ if (evaluation) {
15841
15863
  saveCollaborationState(state, context.rootDir);
15842
15864
  }
15843
- const reviewOnlyAcknowledged = Boolean(options.enforce) && Boolean(options.ackReviewOnly) && isReviewOnlyGuardEvaluation(evaluation);
15844
15865
  const actionCards = buildCollaborationGuardActionCards(evaluation, {
15845
15866
  profile,
15846
15867
  enforced: Boolean(options.enforce),
15847
- ackReviewOnly: Boolean(options.ackReviewOnly),
15868
+ ackReviewOnly: reviewOnlyAcknowledged,
15848
15869
  reviewOnlyAcknowledged
15849
15870
  });
15850
15871
  const guardFailed = shouldFailCollaborationGuard({
15851
15872
  hostedStatus: hosted.status,
15852
15873
  evaluation,
15853
15874
  enforce: Boolean(options.enforce),
15854
- ackReviewOnly: Boolean(options.ackReviewOnly)
15875
+ ackReviewOnly: reviewOnlyAcknowledged
15855
15876
  });
15856
15877
  if (guardFailed) {
15857
15878
  process.exitCode = 2;
@@ -15867,6 +15888,7 @@ async function collaborationGuardCommand(options) {
15867
15888
  enforced: Boolean(options.enforce),
15868
15889
  ackReviewOnly: Boolean(options.ackReviewOnly),
15869
15890
  reviewOnlyAcknowledged,
15891
+ ackSource,
15870
15892
  failed: guardFailed
15871
15893
  }
15872
15894
  },
@@ -16036,7 +16058,8 @@ async function collaborationIdeStatusCommand(options) {
16036
16058
  console.log(`Active leases: ${payload.hosted.activeLeases.length}`);
16037
16059
  }
16038
16060
  function resolveCollaborationContext(options) {
16039
- const rootDir = path19.resolve(options.dir ?? process.cwd());
16061
+ const requestedDir = normalizeOptionalString3(options.dir) ?? normalizeOptionalString3(process.env.SNIPARA_WORKSPACE_DIR) ?? process.cwd();
16062
+ const rootDir = resolveRepoRoot3(requestedDir);
16040
16063
  const config = loadConfig({ cwd: rootDir });
16041
16064
  const actor = buildCollaborationActor(options, config);
16042
16065
  return {
@@ -17138,6 +17161,68 @@ function normalizeLocalLeases(value) {
17138
17161
  return lease;
17139
17162
  }).filter((item) => Boolean(item));
17140
17163
  }
17164
+ function normalizeReviewOnlyAcknowledgements(value, now = /* @__PURE__ */ new Date()) {
17165
+ if (!Array.isArray(value)) {
17166
+ return [];
17167
+ }
17168
+ const nowMs = now.getTime();
17169
+ return value.map((item) => {
17170
+ const parsed = item;
17171
+ const profile = normalizeGuardProfile(parsed.profile);
17172
+ const action = normalizeOptionalString3(parsed.action);
17173
+ const findingFingerprint = normalizeOptionalString3(parsed.findingFingerprint);
17174
+ const acknowledgedAt = normalizeOptionalString3(parsed.acknowledgedAt);
17175
+ const expiresAt = normalizeOptionalString3(parsed.expiresAt);
17176
+ if (!profile || !action || !findingFingerprint || !acknowledgedAt || !expiresAt || !Number.isFinite(Date.parse(expiresAt)) || Date.parse(expiresAt) <= nowMs) {
17177
+ return null;
17178
+ }
17179
+ return { profile, action, findingFingerprint, acknowledgedAt, expiresAt };
17180
+ }).filter((item) => Boolean(item));
17181
+ }
17182
+ function expireReviewOnlyAcknowledgements(state, now = /* @__PURE__ */ new Date()) {
17183
+ state.reviewOnlyAcknowledgements = normalizeReviewOnlyAcknowledgements(
17184
+ state.reviewOnlyAcknowledgements,
17185
+ now
17186
+ );
17187
+ }
17188
+ function fingerprintReviewOnlyGuardEvaluation(evaluation) {
17189
+ const findings = evaluation.conflicts.map((conflict) => ({
17190
+ code: conflict.code,
17191
+ decision: conflict.decision,
17192
+ severity: conflict.severity,
17193
+ resource: `${conflict.resource.kind}:${conflict.resource.id}`,
17194
+ actor: conflict.conflictingActor ? `${conflict.conflictingActor.actorType}:${conflict.conflictingActor.actorId}` : null,
17195
+ workSessionId: conflict.workSessionId ?? null,
17196
+ reason: conflict.reason
17197
+ })).sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
17198
+ return (0, import_node_crypto6.createHash)("sha256").update(JSON.stringify(findings)).digest("hex");
17199
+ }
17200
+ function persistReviewOnlyAcknowledgement(state, profile, action, findingFingerprint, now = /* @__PURE__ */ new Date()) {
17201
+ invalidateReviewOnlyAcknowledgements(state, profile, action);
17202
+ state.reviewOnlyAcknowledgements.push({
17203
+ profile,
17204
+ action,
17205
+ findingFingerprint,
17206
+ acknowledgedAt: now.toISOString(),
17207
+ expiresAt: new Date(now.getTime() + REVIEW_ONLY_ACK_TTL_MS).toISOString()
17208
+ });
17209
+ }
17210
+ function consumeReviewOnlyAcknowledgement(state, profile, action, findingFingerprint) {
17211
+ expireReviewOnlyAcknowledgements(state);
17212
+ const index = state.reviewOnlyAcknowledgements.findIndex(
17213
+ (ack) => ack.profile === profile && ack.action === action && ack.findingFingerprint === findingFingerprint
17214
+ );
17215
+ if (index < 0) {
17216
+ return false;
17217
+ }
17218
+ state.reviewOnlyAcknowledgements.splice(index, 1);
17219
+ return true;
17220
+ }
17221
+ function invalidateReviewOnlyAcknowledgements(state, profile, action) {
17222
+ state.reviewOnlyAcknowledgements = state.reviewOnlyAcknowledgements.filter(
17223
+ (ack) => ack.profile !== profile || ack.action !== action
17224
+ );
17225
+ }
17141
17226
  function normalizeLastGuard(value) {
17142
17227
  const parsed = value;
17143
17228
  if (!parsed || !parsed.decision || !parsed.checkedAt) {
@@ -21549,6 +21634,154 @@ function producerLoopCalibrationStatus(sampleSize, reviewedSampleSize, minReview
21549
21634
  }
21550
21635
  return "reviewable_sample_set";
21551
21636
  }
21637
+ var WORKER_RECEIPT_RELATIVE_DIR = path22.join(".snipara", "orchestrator", "executions");
21638
+ var WORKER_REVIEW_RELATIVE_DIR = path22.join(".snipara", "orchestrator", "reviews");
21639
+ var REQUIRED_WORKER_RECEIPT_FAMILIES = [
21640
+ "handoffReceiptId",
21641
+ "claimId",
21642
+ "proofReceiptIds",
21643
+ "outcomeReceiptId",
21644
+ "brainUpdateReceiptId"
21645
+ ];
21646
+ function listJsonFiles(relativeDir, cwd) {
21647
+ const directory = path22.join(cwd, relativeDir);
21648
+ if (!fs23.existsSync(directory)) {
21649
+ return [];
21650
+ }
21651
+ return fs23.readdirSync(directory, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => path22.join(directory, entry.name)).sort();
21652
+ }
21653
+ function readWorkerReviewFiles(cwd) {
21654
+ const reviews = /* @__PURE__ */ new Map();
21655
+ const invalid = [];
21656
+ for (const filePath of listJsonFiles(WORKER_REVIEW_RELATIVE_DIR, cwd)) {
21657
+ try {
21658
+ const parsed = JSON.parse(fs23.readFileSync(filePath, "utf-8"));
21659
+ if (!isRecord10(parsed)) {
21660
+ throw new Error("review must be a JSON object");
21661
+ }
21662
+ const receiptId = stringValue7(parsed.receiptId);
21663
+ if (!receiptId) {
21664
+ throw new Error("review is missing receiptId");
21665
+ }
21666
+ reviews.set(receiptId, { value: parsed, path: filePath });
21667
+ } catch (error) {
21668
+ invalid.push({
21669
+ path: filePath,
21670
+ relativePath: toProjectRelativePath3(filePath, cwd),
21671
+ error: error instanceof Error ? error.message : String(error)
21672
+ });
21673
+ }
21674
+ }
21675
+ return { reviews, invalid };
21676
+ }
21677
+ function missingWorkerReceiptFamilies(receipt) {
21678
+ const refs = isRecord10(receipt.receiptRefs) ? receipt.receiptRefs : isRecord10(receipt.receipt) ? receipt.receipt : {};
21679
+ return REQUIRED_WORKER_RECEIPT_FAMILIES.filter((family) => {
21680
+ const value = refs[family];
21681
+ return family === "proofReceiptIds" ? !Array.isArray(value) || value.filter((item) => Boolean(stringValue7(item))).length === 0 : !stringValue7(value);
21682
+ });
21683
+ }
21684
+ function summarizeWorkerExecutionReceipts(cwd) {
21685
+ const reviewFiles = readWorkerReviewFiles(cwd);
21686
+ const invalid = [...reviewFiles.invalid];
21687
+ const samples = [];
21688
+ for (const filePath of listJsonFiles(WORKER_RECEIPT_RELATIVE_DIR, cwd)) {
21689
+ try {
21690
+ const parsed = JSON.parse(fs23.readFileSync(filePath, "utf-8"));
21691
+ if (!isRecord10(parsed)) {
21692
+ throw new Error("receipt must be a JSON object");
21693
+ }
21694
+ const receiptId = stringValue7(parsed.receiptId);
21695
+ const schemaVersion = stringValue7(parsed.schemaVersion);
21696
+ if (!receiptId || !schemaVersion) {
21697
+ throw new Error("receipt is missing receiptId or schemaVersion");
21698
+ }
21699
+ const attribution = isRecord10(parsed.workerAttribution) ? parsed.workerAttribution : {};
21700
+ const gate2 = isRecord10(parsed.gate) ? parsed.gate : {};
21701
+ const review = reviewFiles.reviews.get(receiptId);
21702
+ const reviewStatusValue = stringValue7(review?.value.reviewStatus);
21703
+ const reviewStatus = reviewStatusValue === "accepted" ? "accepted" : reviewStatusValue === "blocked" ? "blocked" : "review_pending";
21704
+ const workerId = stringValue7(parsed.workerId) ?? stringValue7(attribution.workerId) ?? stringValue7(parsed.selectedWorkerCandidateId) ?? stringValue7(gate2.selectedWorkerCandidateId) ?? "main_agent";
21705
+ const workCategory = stringValue7(parsed.workCategory) ?? stringValue7(attribution.workCategory) ?? "unknown";
21706
+ const executionActor = stringValue7(parsed.executionActor) ?? stringValue7(attribution.executionActor) ?? (Number(parsed.workersSpawned ?? 0) > 0 ? "worker" : "main_agent");
21707
+ const missingReceiptFamilies = missingWorkerReceiptFamilies(parsed);
21708
+ samples.push({
21709
+ receiptId,
21710
+ schemaVersion,
21711
+ recordedAt: stringValue7(parsed.recordedAt),
21712
+ workerId,
21713
+ workCategory,
21714
+ routingCardRef: stringValue7(parsed.routingCardRef) ?? stringValue7(attribution.routingCardRef),
21715
+ workflowFingerprint: stringValue7(parsed.workflowFingerprint) ?? stringValue7(attribution.workflowFingerprint),
21716
+ executionActor,
21717
+ status: stringValue7(parsed.status),
21718
+ reviewStatus,
21719
+ executed: executionActor === "worker" || Number(parsed.workersSpawned ?? 0) > 0,
21720
+ receiptFamilyComplete: missingReceiptFamilies.length === 0,
21721
+ missingReceiptFamilies,
21722
+ path: filePath,
21723
+ relativePath: toProjectRelativePath3(filePath, cwd),
21724
+ ...review ? {
21725
+ reviewPath: review.path,
21726
+ reviewRelativePath: toProjectRelativePath3(review.path, cwd)
21727
+ } : {}
21728
+ });
21729
+ } catch (error) {
21730
+ invalid.push({
21731
+ path: filePath,
21732
+ relativePath: toProjectRelativePath3(filePath, cwd),
21733
+ error: error instanceof Error ? error.message : String(error)
21734
+ });
21735
+ }
21736
+ }
21737
+ samples.sort(
21738
+ (left, right) => (left.recordedAt ?? left.receiptId).localeCompare(right.recordedAt ?? right.receiptId)
21739
+ );
21740
+ return { samples, invalid };
21741
+ }
21742
+ function buildWorkerTrustRows(samples, minReviewSampleSize) {
21743
+ const grouped = /* @__PURE__ */ new Map();
21744
+ for (const sample of samples) {
21745
+ const key = `${sample.workerId}\0${sample.workCategory}`;
21746
+ grouped.set(key, [...grouped.get(key) ?? [], sample]);
21747
+ }
21748
+ return [...grouped.values()].map((group) => {
21749
+ const reviewedSampleSize = group.filter(
21750
+ (sample) => sample.reviewStatus !== "review_pending"
21751
+ ).length;
21752
+ const verifiedSampleSize = group.filter(
21753
+ (sample) => sample.reviewStatus === "accepted"
21754
+ ).length;
21755
+ const incompleteReceiptSampleSize = group.filter(
21756
+ (sample) => !sample.receiptFamilyComplete
21757
+ ).length;
21758
+ const nextRequired = [
21759
+ reviewedSampleSize < minReviewSampleSize ? `${minReviewSampleSize - reviewedSampleSize} more supervised review sample(s)` : void 0,
21760
+ verifiedSampleSize < 3 ? `${3 - verifiedSampleSize} more accepted verified sample(s)` : void 0,
21761
+ incompleteReceiptSampleSize > 0 ? `${incompleteReceiptSampleSize} sample(s) need complete receipt families` : void 0,
21762
+ group[0].workCategory === "unknown" ? "classify legacy samples by work category" : void 0,
21763
+ "Trust Promotion gate implementation remains required"
21764
+ ].filter((item) => Boolean(item));
21765
+ return {
21766
+ workerId: group[0].workerId,
21767
+ workCategory: group[0].workCategory,
21768
+ state: "probation_supervised",
21769
+ sampleSize: group.length,
21770
+ executedSampleSize: group.filter((sample) => sample.executed).length,
21771
+ reviewedSampleSize,
21772
+ verifiedSampleSize,
21773
+ blockedSampleSize: group.filter((sample) => sample.reviewStatus === "blocked").length,
21774
+ incompleteReceiptSampleSize,
21775
+ workflowFingerprints: uniqueStrings18(
21776
+ group.map((sample) => sample.workflowFingerprint).filter((value) => Boolean(value))
21777
+ ),
21778
+ hardGateReady: false,
21779
+ nextRequired
21780
+ };
21781
+ }).sort(
21782
+ (left, right) => left.workerId === right.workerId ? left.workCategory.localeCompare(right.workCategory) : left.workerId.localeCompare(right.workerId)
21783
+ );
21784
+ }
21552
21785
  function buildProducerLoopReport(options = {}) {
21553
21786
  const cwd = path22.resolve(options.cwd ?? process.cwd());
21554
21787
  const minReviewSampleSize = Math.max(
@@ -21589,12 +21822,16 @@ function buildProducerLoopReport(options = {}) {
21589
21822
  reviewedSampleSize,
21590
21823
  minReviewSampleSize
21591
21824
  );
21825
+ const workerReceiptReport = summarizeWorkerExecutionReceipts(cwd);
21826
+ const workerTrust = buildWorkerTrustRows(workerReceiptReport.samples, minReviewSampleSize);
21592
21827
  const recommendedActions = [
21593
21828
  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
21829
  artifacts.length > 0 && reviewedSampleSize < minReviewSampleSize ? `Review at least ${minReviewSampleSize} local Producer Loop samples before considering any calibration signal.` : void 0,
21595
21830
  unreviewedSampleSize > 0 ? "Mark local samples with workflow producer-review after checking the embedded evidence." : void 0,
21596
21831
  rejectedSampleSize > 0 ? "Inspect rejected Producer Loop samples for false positives, missing context, or reason-code drift." : void 0,
21597
21832
  invalidArtifacts.length > 0 ? "Inspect invalid Producer Loop artifacts before trusting adoption counts." : void 0,
21833
+ workerReceiptReport.samples.length === 0 ? "Run agents execute-gated and persist agents review-gated results to create worker-attributed receipt samples." : void 0,
21834
+ workerReceiptReport.invalid.length > 0 ? "Inspect invalid worker execution receipts or reviews before trusting per-worker counts." : void 0,
21598
21835
  "Review false positives, missing outcomes, and reason-code drift before any future enforcement."
21599
21836
  ].filter((item) => Boolean(item));
21600
21837
  return {
@@ -21616,6 +21853,13 @@ function buildProducerLoopReport(options = {}) {
21616
21853
  reasonCodes: {
21617
21854
  counts: reasonCodeCounts
21618
21855
  },
21856
+ workerReceipts: {
21857
+ sourceDirectories: [WORKER_RECEIPT_RELATIVE_DIR, WORKER_REVIEW_RELATIVE_DIR],
21858
+ sampleSize: workerReceiptReport.samples.length,
21859
+ samples: workerReceiptReport.samples,
21860
+ invalidArtifacts: workerReceiptReport.invalid
21861
+ },
21862
+ workerTrust,
21619
21863
  calibration: {
21620
21864
  status: calibrationStatus,
21621
21865
  sampleSize: artifacts.length,
@@ -21815,6 +22059,19 @@ function printProducerLoopReport(report) {
21815
22059
  if (report.invalidArtifacts.length > 0) {
21816
22060
  printKeyValue2("Invalid artifacts:", report.invalidArtifacts.length);
21817
22061
  }
22062
+ printKeyValue2("Worker receipts:", report.workerReceipts.sampleSize);
22063
+ if (report.workerReceipts.invalidArtifacts.length > 0) {
22064
+ printKeyValue2("Invalid worker evidence:", report.workerReceipts.invalidArtifacts.length);
22065
+ }
22066
+ if (report.workerTrust.length > 0) {
22067
+ console.log("");
22068
+ console.log(import_chalk6.default.bold("Worker Trust (supervised probation)"));
22069
+ for (const row of report.workerTrust) {
22070
+ console.log(
22071
+ `- ${row.workerId} / ${row.workCategory}: ${row.verifiedSampleSize} accepted, ${row.blockedSampleSize} blocked, ${row.reviewedSampleSize}/${row.sampleSize} reviewed`
22072
+ );
22073
+ }
22074
+ }
21818
22075
  if (report.recommendedActions.length > 0) {
21819
22076
  console.log("");
21820
22077
  console.log(import_chalk6.default.bold("Recommended Actions"));
@@ -33407,7 +33664,7 @@ function applyLocalContextMutationPlan(options) {
33407
33664
  writtenFiles
33408
33665
  };
33409
33666
  }
33410
- function listJsonFiles(dir) {
33667
+ function listJsonFiles2(dir) {
33411
33668
  if (!fs30.existsSync(dir)) return [];
33412
33669
  return fs30.readdirSync(dir).filter((name) => name.endsWith(".json")).map((name) => path30.join(dir, name)).sort((left, right) => left.localeCompare(right));
33413
33670
  }
@@ -33534,7 +33791,7 @@ function collectWorkflowDriftSignals(cwd) {
33534
33791
  }
33535
33792
  function collectDecisionDriftSignals(cwd) {
33536
33793
  const pendingDir = path30.join(cwd, ".snipara", "decisions", "pending");
33537
- const pending = listJsonFiles(pendingDir).map((file) => toProjectRelativePath4(file, cwd));
33794
+ const pending = listJsonFiles2(pendingDir).map((file) => toProjectRelativePath4(file, cwd));
33538
33795
  return [
33539
33796
  pending.length > 0 ? {
33540
33797
  id: "decision-requests-pending",
@@ -33558,8 +33815,8 @@ function collectDecisionDriftSignals(cwd) {
33558
33815
  ];
33559
33816
  }
33560
33817
  function collectContextControlDriftSignals(cwd) {
33561
- const planFiles = listJsonFiles(path30.join(cwd, CONTEXT_CONTROL_PLANS_RELATIVE_DIR));
33562
- const receiptFiles = listJsonFiles(path30.join(cwd, CONTEXT_CONTROL_APPLIED_RELATIVE_DIR));
33818
+ const planFiles = listJsonFiles2(path30.join(cwd, CONTEXT_CONTROL_PLANS_RELATIVE_DIR));
33819
+ const receiptFiles = listJsonFiles2(path30.join(cwd, CONTEXT_CONTROL_APPLIED_RELATIVE_DIR));
33563
33820
  const receiptPlanHashes = new Set(
33564
33821
  receiptFiles.map((file) => readJsonFileSafe(file).value).filter(isContextMutationApplyReceipt).map((receipt) => receipt.planHash)
33565
33822
  );
@@ -37325,7 +37582,7 @@ collaboration.command("guard").description("Check files or resources against act
37325
37582
  "edit"
37326
37583
  ).option("-a, --action <action>", "Guarded action label", "edit").option("--actor <actor>", "Developer or agent display name").option("--actor-id <actorId>", "Stable developer or agent id").option("--actor-type <actorType>", "HUMAN|AGENT|SYSTEM", "AGENT").option("--session-id <sessionId>", "Automation/session id").option("--work-session-id <workSessionId>", "Existing hosted work session id").option("--client <client>", "Client label", "snipara-companion").option("--repository <repository>", "Repository id").option("-b, --branch <branch>", "Current branch name").option("--worktree <worktree>", "Worktree path").option("--max-files <number>", "Maximum files for local code resource expansion", "2000").option("--no-persist", "Evaluate without storing a guard event").option("--enforce", "Exit non-zero for REVIEW_REQUIRED or REQUIRES_ACK, not only BLOCKED").option(
37327
37584
  "--ack-review-only",
37328
- "Under --enforce, acknowledge review-only stale-state and decision-consistency warnings"
37585
+ "Under --enforce, persist one exact review-only acknowledgement for the next hook rerun"
37329
37586
  ).option("-d, --dir <directory>", "Repository directory (default: current)").option("--json", "Print raw JSON").option("--verbose", "Print the full guard report even when the guard passes").action(async (options) => {
37330
37587
  await collaborationGuardCommand({
37331
37588
  files: options.files,
@@ -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snipara-companion",
3
- "version": "3.2.28",
3
+ "version": "3.2.30",
4
4
  "description": "Local-first CLI that asks your repo what breaks before an AI coding agent edits it.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {