@mcptoolshop/research-os 0.3.0 → 0.3.1

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/index.js CHANGED
@@ -112,7 +112,7 @@ var init_errors = __esm({
112
112
 
113
113
  // src/intake/schema.ts
114
114
  import { z } from "zod";
115
- var SectionStatusSchema, SectionSchema, SourceFloorGateSchema, ClaimIntegrityGateSchema, FreshnessGateSchema, ContradictionGateSchema, SectionBudgetGateSchema, GateConfigSchema, PrimarySourceWaiverSchema, FreshnessRequirementsSchema, ReviewProfilePresetSchema, DEFAULT_REVIEW_PROFILES, ResearchYamlSchema;
115
+ var SectionStatusSchema, SectionSchema, SourceFloorGateSchema, ClaimIntegrityGateSchema, FreshnessGateSchema, ContradictionGateSchema, SectionBudgetGateSchema, GateConfigSchema, SectionScopedWaiverSchema, PrimarySourceWaiverSchema, FreshnessRequirementsSchema, ReviewProfilePresetSchema, DEFAULT_REVIEW_PROFILES, ResearchYamlSchema;
116
116
  var init_schema = __esm({
117
117
  "src/intake/schema.ts"() {
118
118
  "use strict";
@@ -162,10 +162,20 @@ var init_schema = __esm({
162
162
  contradiction: ContradictionGateSchema.default({}),
163
163
  section_budget: SectionBudgetGateSchema.default({})
164
164
  });
165
+ SectionScopedWaiverSchema = z.object({
166
+ section_id: z.string().regex(/^[0-9]{2}-[a-z0-9-]+$/, 'Section id must look like "01-landscape"'),
167
+ scope: z.enum(["min_independent_publishers", "primary_sources_required"]),
168
+ reason: z.string().min(1),
169
+ compensating_controls: z.array(z.string()).min(1)
170
+ });
165
171
  PrimarySourceWaiverSchema = z.object({
166
172
  status: z.enum(["none", "requested", "granted"]).default("none"),
167
173
  reason: z.string().optional(),
168
- compensating_controls: z.array(z.string()).default([])
174
+ compensating_controls: z.array(z.string()).default([]),
175
+ // Section-scoped waivers; each entry is its own waiver record. Independent
176
+ // of the pack-level status/reason/compensating_controls fields above.
177
+ // Defaults to [] for backward compatibility — existing packs unaffected.
178
+ section_waivers: z.array(SectionScopedWaiverSchema).default([])
169
179
  });
170
180
  FreshnessRequirementsSchema = z.object({
171
181
  required: z.boolean().default(true),
@@ -4222,9 +4232,28 @@ function applyWaivers(input, results) {
4222
4232
  const updated = results.map((r) => ({ ...r }));
4223
4233
  const applied = [];
4224
4234
  const validationFailures = [];
4225
- if (waiver.status !== "granted") {
4226
- return { updatedResults: updated, waivers_applied: applied, waiver_validation_failures: validationFailures };
4235
+ applyPackLevelWaiver({
4236
+ waiver,
4237
+ cfg,
4238
+ updated,
4239
+ applied,
4240
+ validationFailures
4241
+ });
4242
+ for (const sw of waiver.section_waivers ?? []) {
4243
+ if (sw.section_id !== input.section.id) continue;
4244
+ applySectionScopedWaiver({
4245
+ waiver: sw,
4246
+ cfg,
4247
+ updated,
4248
+ applied,
4249
+ validationFailures
4250
+ });
4227
4251
  }
4252
+ return { updatedResults: updated, waivers_applied: applied, waiver_validation_failures: validationFailures };
4253
+ }
4254
+ function applyPackLevelWaiver(args) {
4255
+ const { waiver, cfg, updated, applied, validationFailures } = args;
4256
+ if (waiver.status !== "granted") return;
4228
4257
  if (!waiver.reason || waiver.reason.trim().length === 0) {
4229
4258
  validationFailures.push({
4230
4259
  family: "waivers",
@@ -4234,7 +4263,7 @@ function applyWaivers(input, results) {
4234
4263
  evidence: [],
4235
4264
  blocks_synthesis: true
4236
4265
  });
4237
- return { updatedResults: updated, waivers_applied: applied, waiver_validation_failures: validationFailures };
4266
+ return;
4238
4267
  }
4239
4268
  if (waiver.compensating_controls.length === 0) {
4240
4269
  validationFailures.push({
@@ -4245,7 +4274,7 @@ function applyWaivers(input, results) {
4245
4274
  evidence: [],
4246
4275
  blocks_synthesis: true
4247
4276
  });
4248
- return { updatedResults: updated, waivers_applied: applied, waiver_validation_failures: validationFailures };
4277
+ return;
4249
4278
  }
4250
4279
  if (!cfg.primary_source_waiver_allowed) {
4251
4280
  validationFailures.push({
@@ -4256,7 +4285,7 @@ function applyWaivers(input, results) {
4256
4285
  evidence: [],
4257
4286
  blocks_synthesis: true
4258
4287
  });
4259
- return { updatedResults: updated, waivers_applied: applied, waiver_validation_failures: validationFailures };
4288
+ return;
4260
4289
  }
4261
4290
  for (let i = 0; i < updated.length; i += 1) {
4262
4291
  const r = updated[i];
@@ -4278,7 +4307,62 @@ function applyWaivers(input, results) {
4278
4307
  });
4279
4308
  }
4280
4309
  }
4281
- return { updatedResults: updated, waivers_applied: applied, waiver_validation_failures: validationFailures };
4310
+ }
4311
+ function applySectionScopedWaiver(args) {
4312
+ const { waiver, cfg, updated, applied, validationFailures } = args;
4313
+ if (!waiver.reason || waiver.reason.trim().length === 0) {
4314
+ validationFailures.push({
4315
+ family: "waivers",
4316
+ check: "section_scoped_waiver_reason_required",
4317
+ status: "fail",
4318
+ detail: `Section-scoped waiver for ${waiver.section_id}/${waiver.scope} is missing a reason. Waiver is invalid; original failure stands.`,
4319
+ evidence: [waiver.section_id],
4320
+ blocks_synthesis: true
4321
+ });
4322
+ return;
4323
+ }
4324
+ if (waiver.compensating_controls.length === 0) {
4325
+ validationFailures.push({
4326
+ family: "waivers",
4327
+ check: "section_scoped_waiver_compensating_controls_required",
4328
+ status: "fail",
4329
+ detail: `Section-scoped waiver for ${waiver.section_id}/${waiver.scope} has empty compensating_controls. Waiver is invalid; original failure stands.`,
4330
+ evidence: [waiver.section_id],
4331
+ blocks_synthesis: true
4332
+ });
4333
+ return;
4334
+ }
4335
+ if (!cfg.primary_source_waiver_allowed) {
4336
+ validationFailures.push({
4337
+ family: "waivers",
4338
+ check: "section_scoped_waiver_allowed_by_pack",
4339
+ status: "fail",
4340
+ detail: `Pack policy gates.source_floor.primary_source_waiver_allowed=false; section-scoped waiver for ${waiver.section_id}/${waiver.scope} cannot be applied.`,
4341
+ evidence: [waiver.section_id],
4342
+ blocks_synthesis: true
4343
+ });
4344
+ return;
4345
+ }
4346
+ for (let i = 0; i < updated.length; i += 1) {
4347
+ const r = updated[i];
4348
+ if (r.family === "source_floor" && r.check === waiver.scope && r.status === "fail") {
4349
+ const original = r.status;
4350
+ updated[i] = {
4351
+ ...r,
4352
+ status: "pass_with_waiver",
4353
+ detail: `${r.detail} Section-scoped waiver granted for ${waiver.section_id} with ${waiver.compensating_controls.length} compensating control(s); converted from fail to pass_with_waiver.`,
4354
+ blocks_synthesis: false
4355
+ };
4356
+ applied.push({
4357
+ family: "source_floor",
4358
+ check: waiver.scope,
4359
+ reason: waiver.reason,
4360
+ compensating_controls: waiver.compensating_controls,
4361
+ original_status: original,
4362
+ new_status: "pass_with_waiver"
4363
+ });
4364
+ }
4365
+ }
4282
4366
  }
4283
4367
  var init_waivers = __esm({
4284
4368
  "src/gates/checks/waivers.ts"() {
@@ -5535,9 +5619,11 @@ function pickHighestPriority(decisions) {
5535
5619
  return "accepted_for_synthesis";
5536
5620
  }
5537
5621
  function deriveClaimReviews(args) {
5538
- const { claims, findings, reviewer, reviewMethod } = args;
5622
+ const { claims, findings, reviewer, reviewMethod, activeSectionWaivers } = args;
5539
5623
  const reviews = [];
5540
5624
  const now = (/* @__PURE__ */ new Date()).toISOString();
5625
+ const monopolyWaived = Array.isArray(activeSectionWaivers) && activeSectionWaivers.some((w) => w.scope === "min_independent_publishers");
5626
+ const isWaivedFinding = (f) => monopolyWaived && f.category === "source_cluster_monopoly";
5541
5627
  for (const claim of claims) {
5542
5628
  const claimFindings = findings.filter((f) => f.claim_ids.includes(claim.claim_id));
5543
5629
  if (claimFindings.length === 0) {
@@ -5554,6 +5640,7 @@ function deriveClaimReviews(args) {
5554
5640
  }
5555
5641
  let decisions = [];
5556
5642
  for (const f of claimFindings) {
5643
+ if (isWaivedFinding(f)) continue;
5557
5644
  if (f.severity === "block") {
5558
5645
  decisions.push(BLOCK_TO_DECISION[f.category] ?? "rejected");
5559
5646
  } else if (f.severity === "warn") {
@@ -5570,7 +5657,9 @@ function deriveClaimReviews(args) {
5570
5657
  );
5571
5658
  }
5572
5659
  const decision = pickHighestPriority(decisions);
5573
- const reasonParts = claimFindings.filter((f) => f.severity !== "info").map((f) => `${f.category} (${f.severity})`);
5660
+ const reasonParts = claimFindings.filter((f) => f.severity !== "info").map(
5661
+ (f) => isWaivedFinding(f) ? `${f.category} (${f.severity}, waived)` : `${f.category} (${f.severity})`
5662
+ );
5574
5663
  const reason = reasonParts.length > 0 ? `Findings: ${reasonParts.join("; ")}.` : "Only info-level findings; accepted.";
5575
5664
  reviews.push({
5576
5665
  claim_id: claim.claim_id,
@@ -5951,7 +6040,8 @@ async function review(options) {
5951
6040
  candidateClaims,
5952
6041
  drafts: acceptedDrafts,
5953
6042
  llmFindingsRejected,
5954
- profile: options.profile ?? DEFAULT_PROFILE
6043
+ profile: options.profile ?? DEFAULT_PROFILE,
6044
+ research
5955
6045
  });
5956
6046
  }
5957
6047
  async function runMultiPassReview(args) {
@@ -6013,7 +6103,8 @@ async function runMultiPassReview(args) {
6013
6103
  candidateClaims: args.candidateClaims,
6014
6104
  drafts: merged,
6015
6105
  llmFindingsRejected,
6016
- profile: args.options.profile ?? DEFAULT_PROFILE
6106
+ profile: args.options.profile ?? DEFAULT_PROFILE,
6107
+ research: args.research
6017
6108
  });
6018
6109
  }
6019
6110
  async function reviewWithSpecificReviewer(args) {
@@ -6041,7 +6132,8 @@ async function reviewWithSpecificReviewer(args) {
6041
6132
  candidateClaims: args.candidateClaims,
6042
6133
  drafts: result.drafts,
6043
6134
  llmFindingsRejected: 0,
6044
- profile: args.options.profile ?? DEFAULT_PROFILE
6135
+ profile: args.options.profile ?? DEFAULT_PROFILE,
6136
+ research: args.research
6045
6137
  });
6046
6138
  }
6047
6139
  async function finalizeReview(args) {
@@ -6064,11 +6156,15 @@ async function finalizeReview(args) {
6064
6156
  seen.add(f.finding_id);
6065
6157
  dedupedFindings.push(f);
6066
6158
  }
6159
+ const activeSectionWaivers = args.research.primary_source_waiver.section_waivers.filter(
6160
+ (w) => w.section_id === args.sectionId
6161
+ );
6067
6162
  const claimReviews = deriveClaimReviews({
6068
6163
  claims: args.candidateClaims,
6069
6164
  findings: dedupedFindings,
6070
6165
  reviewer: args.reviewer,
6071
- reviewMethod: args.reviewMethod
6166
+ reviewMethod: args.reviewMethod,
6167
+ activeSectionWaivers
6072
6168
  });
6073
6169
  const decisionCounts = {
6074
6170
  accepted_for_synthesis: 0,
@@ -8795,6 +8891,19 @@ function buildStaleSources(input) {
8795
8891
  }
8796
8892
  return out;
8797
8893
  }
8894
+ function findSectionWaiver(research, sectionId, scope) {
8895
+ return (research.primary_source_waiver.section_waivers ?? []).find(
8896
+ (w) => w.section_id === sectionId && w.scope === scope
8897
+ );
8898
+ }
8899
+ function annotateWeakSource(row, waiver) {
8900
+ if (!waiver) return row;
8901
+ return { ...row, waived: true, waiver_reason: waiver.reason };
8902
+ }
8903
+ function annotateDiversityGap(row, waiver) {
8904
+ if (!waiver) return row;
8905
+ return { ...row, waived: true, waiver_reason: waiver.reason };
8906
+ }
8798
8907
  function buildWeakSources(input) {
8799
8908
  const out = [];
8800
8909
  const cfg = input.research.gates.source_floor;
@@ -8804,33 +8913,50 @@ function buildWeakSources(input) {
8804
8913
  const publishers = new Set(
8805
8914
  sectionSources.map((c) => c.publisher).filter((p) => typeof p === "string")
8806
8915
  );
8916
+ const monopolyWaiver = findSectionWaiver(input.research, sid, "min_independent_publishers");
8917
+ const primaryWaiver = findSectionWaiver(input.research, sid, "primary_sources_required");
8807
8918
  if (publishers.size === 1 && sectionSources.length >= 2) {
8808
- out.push({
8809
- reason: "source_cluster_monopoly",
8810
- section_id: sid,
8811
- details: `Every source in this section traces to a single publisher (${[...publishers][0]}).`,
8812
- evidence_ids: sectionSources.map((s) => s.source_id),
8813
- artifact_path: `sections/${sid}/sources.jsonl`
8814
- });
8919
+ out.push(
8920
+ annotateWeakSource(
8921
+ {
8922
+ reason: "source_cluster_monopoly",
8923
+ section_id: sid,
8924
+ details: `Every source in this section traces to a single publisher (${[...publishers][0]}).`,
8925
+ evidence_ids: sectionSources.map((s) => s.source_id),
8926
+ artifact_path: `sections/${sid}/sources.jsonl`
8927
+ },
8928
+ monopolyWaiver
8929
+ )
8930
+ );
8815
8931
  }
8816
8932
  if (publishers.size < cfg.min_independent_publishers) {
8817
- out.push({
8818
- reason: "low_independent_publishers",
8819
- section_id: sid,
8820
- details: `${publishers.size} independent publisher(s) \u2014 pack policy requires at least ${cfg.min_independent_publishers}.`,
8821
- evidence_ids: [...publishers],
8822
- artifact_path: `sections/${sid}/sources.jsonl`
8823
- });
8933
+ out.push(
8934
+ annotateWeakSource(
8935
+ {
8936
+ reason: "low_independent_publishers",
8937
+ section_id: sid,
8938
+ details: `${publishers.size} independent publisher(s) \u2014 pack policy requires at least ${cfg.min_independent_publishers}.`,
8939
+ evidence_ids: [...publishers],
8940
+ artifact_path: `sections/${sid}/sources.jsonl`
8941
+ },
8942
+ monopolyWaiver
8943
+ )
8944
+ );
8824
8945
  }
8825
8946
  const primary = sectionSources.filter((c) => c.source_type === "primary").length;
8826
8947
  if (primary < cfg.primary_sources_required) {
8827
- out.push({
8828
- reason: "missing_primary_source",
8829
- section_id: sid,
8830
- details: `${primary} primary source(s) \u2014 pack policy requires at least ${cfg.primary_sources_required}.`,
8831
- evidence_ids: sectionSources.filter((c) => c.source_type === "primary").map((c) => c.source_id),
8832
- artifact_path: `sections/${sid}/sources.jsonl`
8833
- });
8948
+ out.push(
8949
+ annotateWeakSource(
8950
+ {
8951
+ reason: "missing_primary_source",
8952
+ section_id: sid,
8953
+ details: `${primary} primary source(s) \u2014 pack policy requires at least ${cfg.primary_sources_required}.`,
8954
+ evidence_ids: sectionSources.filter((c) => c.source_type === "primary").map((c) => c.source_id),
8955
+ artifact_path: `sections/${sid}/sources.jsonl`
8956
+ },
8957
+ primaryWaiver
8958
+ )
8959
+ );
8834
8960
  }
8835
8961
  const types = /* @__PURE__ */ new Map();
8836
8962
  for (const c of sectionSources) types.set(c.source_type, (types.get(c.source_type) ?? 0) + 1);
@@ -8945,20 +9071,31 @@ function buildSourceDiversityGaps(input) {
8945
9071
  });
8946
9072
  continue;
8947
9073
  }
9074
+ const monopolyWaiver = findSectionWaiver(input.research, sid, "min_independent_publishers");
8948
9075
  if (publishers.size === 1 && sectionSources.length >= 2) {
8949
- out.push({
8950
- reason: "section_publisher_monopoly",
8951
- section_id: sid,
8952
- details: `Section sources monopolized by ${[...publishers][0]}.`,
8953
- evidence_ids: sectionSources.map((s) => s.source_id)
8954
- });
9076
+ out.push(
9077
+ annotateDiversityGap(
9078
+ {
9079
+ reason: "section_publisher_monopoly",
9080
+ section_id: sid,
9081
+ details: `Section sources monopolized by ${[...publishers][0]}.`,
9082
+ evidence_ids: sectionSources.map((s) => s.source_id)
9083
+ },
9084
+ monopolyWaiver
9085
+ )
9086
+ );
8955
9087
  } else if (publishers.size < cfg.min_independent_publishers) {
8956
- out.push({
8957
- reason: "low_section_publisher_count",
8958
- section_id: sid,
8959
- details: `${publishers.size} publisher(s); pack policy requires ${cfg.min_independent_publishers}.`,
8960
- evidence_ids: [...publishers]
8961
- });
9088
+ out.push(
9089
+ annotateDiversityGap(
9090
+ {
9091
+ reason: "low_section_publisher_count",
9092
+ section_id: sid,
9093
+ details: `${publishers.size} publisher(s); pack policy requires ${cfg.min_independent_publishers}.`,
9094
+ evidence_ids: [...publishers]
9095
+ },
9096
+ monopolyWaiver
9097
+ )
9098
+ );
8962
9099
  }
8963
9100
  }
8964
9101
  const pubSectionCount = /* @__PURE__ */ new Map();
@@ -12165,7 +12302,7 @@ var init_src = __esm({
12165
12302
  init_triage();
12166
12303
  init_discover();
12167
12304
  init_errors();
12168
- RESEARCH_OS_VERSION = "0.3.0";
12305
+ RESEARCH_OS_VERSION = "0.3.1";
12169
12306
  }
12170
12307
  });
12171
12308
  init_src();