@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/CHANGELOG.md +116 -0
- package/README.md +6 -4
- package/dist/cli.js +186 -48
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +174 -84
- package/dist/index.js +185 -48
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,122 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `research-os` are documented here.
|
|
4
4
|
|
|
5
|
+
## [0.3.1] — 2026-05-09
|
|
6
|
+
|
|
7
|
+
Tight release. One real, tested, dogfooded improvement: section-scoped
|
|
8
|
+
source-floor waivers + reviewer-side acknowledgement. Earned by Experiment 3
|
|
9
|
+
XRPL pack Session 2 — canonical-protocol sections (XRPL XLS standards,
|
|
10
|
+
single-foundation chain documentation, walled-garden API specs) inverted
|
|
11
|
+
the assumption that publisher diversity is a proxy for truth quality. No
|
|
12
|
+
other v0.3.x candidates shipped — F-01 (init version-stamp), F-02
|
|
13
|
+
(packs-dir docs), F-05 (discover --query example), F-08 (Windows process
|
|
14
|
+
recovery), F-16 (unused SectionSchema fields), F-17 (sections/<id>/gates.yaml
|
|
15
|
+
runtime wiring) are deferred to their own scoped releases.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **`primary_source_waiver.section_waivers[]`** — section-scoped source-floor
|
|
20
|
+
waivers. Each entry carries `section_id`, `scope` (`min_independent_publishers`
|
|
21
|
+
or `primary_sources_required`), `reason` (non-empty), and
|
|
22
|
+
`compensating_controls[]` (at least one entry). Schema enforcement: empty
|
|
23
|
+
`reason` or empty `compensating_controls[]` fail validation. Pack policy
|
|
24
|
+
`gates.source_floor.primary_source_waiver_allowed: false` blocks both
|
|
25
|
+
pack-level and section-scoped waivers — operators cannot smuggle a waiver
|
|
26
|
+
past pack policy by rerouting it to section scope.
|
|
27
|
+
|
|
28
|
+
Multiple entries can target different sections, or the same section with
|
|
29
|
+
different scopes. Pack-level `primary_source_waiver` semantics unchanged;
|
|
30
|
+
the new `section_waivers[]` is additive and defaults to `[]` for backward
|
|
31
|
+
compatibility. Existing packs unaffected. Full reference:
|
|
32
|
+
[`docs/section-scoped-waivers.md`](docs/section-scoped-waivers.md).
|
|
33
|
+
|
|
34
|
+
- **Reviewer-side acknowledgement** — when a section has a matching
|
|
35
|
+
`min_independent_publishers` waiver in effect, the calibrated reviewer's
|
|
36
|
+
section-wide `source_cluster_monopoly` finding remains visible in the
|
|
37
|
+
findings ledger but does NOT, by itself, route claims to
|
|
38
|
+
`needs_source_repair`. The finding is annotated as
|
|
39
|
+
`(severity, waived)` in the claim-review's reason string so operators
|
|
40
|
+
reading the ledger can see the finding is present but neutralised. Other
|
|
41
|
+
source-quality findings (per-claim `source_quality_problem`,
|
|
42
|
+
`scope_widening`, `overgeneralized_claim`, etc.) continue to drive their
|
|
43
|
+
own routing normally.
|
|
44
|
+
|
|
45
|
+
- **Audit-side disclosure** — `weak-sources.{json,md}` and
|
|
46
|
+
`source-diversity-gaps.{json,md}` rollups annotate waived rows with
|
|
47
|
+
`waived: true` and `waiver_reason: <verbatim>` when a matching section
|
|
48
|
+
waiver is active. Rows are NOT removed (Law 16: waivers do not hide
|
|
49
|
+
evidence). The publisher-monopoly fact is still surfaced in the rollup;
|
|
50
|
+
it's disclosed as deliberately accepted rather than as an open blocker.
|
|
51
|
+
|
|
52
|
+
- **13 new tests** in `test/section-scoped-waivers.test.ts` covering
|
|
53
|
+
schema validation (valid shape, missing reason, empty compensating
|
|
54
|
+
controls, invalid scope enum, bad section_id regex), gate-side
|
|
55
|
+
conversion (section_id match, section_id mismatch, primary_sources_required
|
|
56
|
+
scope, pack-level regression, pack-policy refusal, multiple sections,
|
|
57
|
+
multiple scopes for same section, disclosure in WaiverApplication),
|
|
58
|
+
reviewer-side acknowledgement (waived monopoly → accepted, per-claim
|
|
59
|
+
quality still routes, regression without waiver), and audit-side
|
|
60
|
+
annotation.
|
|
61
|
+
|
|
62
|
+
- **`docs/section-scoped-waivers.md`** — full operator reference: schema,
|
|
63
|
+
behavior contract, valid-cases / invalid-cases enumeration, required
|
|
64
|
+
operator discipline (synthesis-time disclosure beyond the schema), and
|
|
65
|
+
the release thesis. Opens with the canonical phrasing:
|
|
66
|
+
*"Use section-scoped source waivers when publisher diversity is
|
|
67
|
+
structurally incompatible with the section's truth source, not when a
|
|
68
|
+
section merely failed to find enough sources."*
|
|
69
|
+
|
|
70
|
+
- **Handbook page** at `/handbook/section-scoped-waivers` — condensed
|
|
71
|
+
reference matching the docs page.
|
|
72
|
+
|
|
73
|
+
### Changed
|
|
74
|
+
|
|
75
|
+
- **Pack-level `min_independent_publishers: 0` workaround DEPRECATED**
|
|
76
|
+
in the canonical `research-packs/docs/operator-playbook.md` and the
|
|
77
|
+
research-os handbook mirror. The pack-level pattern remains valid for
|
|
78
|
+
already-frozen packs (e.g., `packages/comfyui-workflow-durability/`)
|
|
79
|
+
whose freeze receipts are unchanged; new packs should prefer the
|
|
80
|
+
section-scoped pattern. Forward notes added to
|
|
81
|
+
[`docs/experiment-1-proof.md`](docs/experiment-1-proof.md) at the
|
|
82
|
+
references to the deprecated workaround — historical content
|
|
83
|
+
preserved, not rewritten.
|
|
84
|
+
|
|
85
|
+
### Documentation
|
|
86
|
+
|
|
87
|
+
- README status block updated to v0.3.1; version badge updated.
|
|
88
|
+
- `docs/roadmap.md` Experiment 3 progress: section-scoped source waivers
|
|
89
|
+
shipped in v0.3.1; pack #2 of 3 (XRPL) earned both v0.3.0 and v0.3.1
|
|
90
|
+
fixes. Two more external-domain packs required for closure.
|
|
91
|
+
- **Cross-repo:** `research-packs/docs/operator-playbook.md` updated in
|
|
92
|
+
the same release window. Adds the section-scoped waiver pattern as the
|
|
93
|
+
canonical guidance with the same anti-misuse framing as the research-os
|
|
94
|
+
docs page (public guidance is consistent across the surface by design).
|
|
95
|
+
Deprecates the `min_independent_publishers: 0` pack-level workaround.
|
|
96
|
+
|
|
97
|
+
### Tests
|
|
98
|
+
|
|
99
|
+
- **540 total** (527 at v0.3.0 → 540 at v0.3.1, +13 from
|
|
100
|
+
`test/section-scoped-waivers.test.ts`).
|
|
101
|
+
|
|
102
|
+
### Migration notes
|
|
103
|
+
|
|
104
|
+
No code-level migration required. Existing packs continue to work
|
|
105
|
+
unchanged — `section_waivers` defaults to `[]`. Frozen packs' freeze
|
|
106
|
+
receipts remain valid (the schema addition is additive).
|
|
107
|
+
|
|
108
|
+
For new canonical-protocol packs: prefer section-scoped waivers over the
|
|
109
|
+
deprecated pack-level `min_independent_publishers: 0` workaround. The
|
|
110
|
+
section-scoped pattern preserves the publisher-diversity floor on every
|
|
111
|
+
section that doesn't waive it explicitly.
|
|
112
|
+
|
|
113
|
+
For operators with packs already using the deprecated pack-level
|
|
114
|
+
workaround: the pattern remains valid; no migration is required. If you
|
|
115
|
+
want to tighten the global default and waive specific sections instead,
|
|
116
|
+
that's a clean per-section migration — set
|
|
117
|
+
`min_independent_publishers` back to its non-zero pack default and add
|
|
118
|
+
section_waivers entries for the sections that need them, with
|
|
119
|
+
`reason` and `compensating_controls[]` documented.
|
|
120
|
+
|
|
5
121
|
## [0.3.0] — 2026-05-09
|
|
6
122
|
|
|
7
123
|
Tight release. One real, tested, dogfooded improvement: `--detector` flag on
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
</p>
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
|
-
<a href="https://github.com/mcp-tool-shop-org/research-os/releases/tag/v0.3.
|
|
10
|
+
<a href="https://github.com/mcp-tool-shop-org/research-os/releases/tag/v0.3.1"><img src="https://img.shields.io/badge/version-0.3.1-blue" alt="version 0.3.1"></a>
|
|
11
11
|
<a href="https://github.com/mcp-tool-shop-org/research-os/actions/workflows/ci.yml"><img src="https://github.com/mcp-tool-shop-org/research-os/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
12
12
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="MIT License"></a>
|
|
13
13
|
<img src="https://img.shields.io/badge/node-%E2%89%A520-brightgreen" alt="Node ≥20">
|
|
@@ -151,9 +151,11 @@ This is the structural alternative to *search → summarize → pretty report*.
|
|
|
151
151
|
|
|
152
152
|
## Status
|
|
153
153
|
|
|
154
|
-
**v0.3.
|
|
154
|
+
**v0.3.1** — published to npm as `@mcptoolshop/research-os@0.3.1`, 2026-05-09. Ships section-scoped source-floor waivers (`primary_source_waiver.section_waivers[]`) plus reviewer-side acknowledgement so a waived section-wide `source_cluster_monopoly` finding becomes a visible caveat rather than auto-routing all claims to `needs_source_repair`. Earned by Experiment 3 XRPL pack Session 2 — canonical-protocol sections (single-foundation chains, walled-garden API specs, standards-body docs) inverted the assumption that publisher diversity is a proxy for truth quality. 540/540 vitest passing. See [CHANGELOG.md](CHANGELOG.md) and [`docs/section-scoped-waivers.md`](docs/section-scoped-waivers.md).
|
|
155
155
|
|
|
156
|
-
|
|
156
|
+
**Section-scoped source waivers** — Use them when publisher diversity is structurally incompatible with the section's truth source, not when a section merely failed to find enough sources. Schema-enforced `reason` + non-empty `compensating_controls[]`. Pack policy `primary_source_waiver_allowed: false` blocks both pack-level and section-scoped waivers. The pre-v0.3.1 pack-level `min_independent_publishers: 0` workaround is now deprecated; existing frozen packs remain valid under their existing receipts. See [`docs/section-scoped-waivers.md`](docs/section-scoped-waivers.md) and the [research-packs operator playbook](https://github.com/mcp-tool-shop-org/research-packs/blob/main/docs/operator-playbook.md).
|
|
157
|
+
|
|
158
|
+
**v0.3.0** — published 2026-05-09. Shipped the `--detector <auto|heuristic|ollama-intern>` flag on `contradict map` (F-09 chain-blocker fix from Experiment 3 Session 1, XRPL pack). 527/527 vitest then. Detector selection is now an explicit operator choice instead of a state-dependent env-var dance; mode is announced visibly on every run. See [`docs/contradict-map.md`](docs/contradict-map.md).
|
|
157
159
|
|
|
158
160
|
**v0.2.0** — published 2026-05-09. Shipped `research-os pack publish` (Experiment 2) and the Pattern 2 readiness predicate fix. 515/515 vitest passing then. See [CHANGELOG.md](CHANGELOG.md). Frozen packs export to the canonical `research-packs` archive with a single command; admission contract is enforced by code, not checklist. See [`docs/pack-publish.md`](docs/pack-publish.md).
|
|
159
161
|
|
|
@@ -165,7 +167,7 @@ This is the structural alternative to *search → summarize → pretty report*.
|
|
|
165
167
|
|
|
166
168
|
### What v0.3 is not
|
|
167
169
|
|
|
168
|
-
- Not battle-tested by external users. Two dogfood arcs have closed — one self-referential, one external-domain — and Experiment 3 (API stability under external pressure) is in progress: pack #1 of 3 (XRPL creator-token durability) earned the v0.3.0 `--detector` flag. Two more external-domain packs required for Experiment 3 closure.
|
|
170
|
+
- Not battle-tested by external users. Two dogfood arcs have closed — one self-referential, one external-domain — and Experiment 3 (API stability under external pressure) is in progress: pack #1 of 3 (XRPL creator-token durability) earned both the v0.3.0 `--detector` flag and the v0.3.1 section-scoped source waivers. Two more external-domain packs required for Experiment 3 closure.
|
|
169
171
|
- Not a synthesis writer. The `synth workspace` command generates the structured workspace; humans (or Cowork) write the prose against accepted claim IDs.
|
|
170
172
|
- Not API-stable under semver. v1.0.0 is an earned state, not a calendar date — see [`docs/roadmap.md`](docs/roadmap.md) for the six experiments that close the gap.
|
|
171
173
|
|
package/dist/cli.js
CHANGED
|
@@ -106,11 +106,12 @@ __export(schema_exports, {
|
|
|
106
106
|
ReviewProfilePresetSchema: () => ReviewProfilePresetSchema,
|
|
107
107
|
SectionBudgetGateSchema: () => SectionBudgetGateSchema,
|
|
108
108
|
SectionSchema: () => SectionSchema,
|
|
109
|
+
SectionScopedWaiverSchema: () => SectionScopedWaiverSchema,
|
|
109
110
|
SectionStatusSchema: () => SectionStatusSchema,
|
|
110
111
|
SourceFloorGateSchema: () => SourceFloorGateSchema
|
|
111
112
|
});
|
|
112
113
|
import { z } from "zod";
|
|
113
|
-
var SectionStatusSchema, SectionSchema, SourceFloorGateSchema, ClaimIntegrityGateSchema, FreshnessGateSchema, ContradictionGateSchema, SectionBudgetGateSchema, GateConfigSchema, PrimarySourceWaiverSchema, FreshnessRequirementsSchema, ReviewProfilePresetSchema, DEFAULT_REVIEW_PROFILES, ResearchYamlSchema;
|
|
114
|
+
var SectionStatusSchema, SectionSchema, SourceFloorGateSchema, ClaimIntegrityGateSchema, FreshnessGateSchema, ContradictionGateSchema, SectionBudgetGateSchema, GateConfigSchema, SectionScopedWaiverSchema, PrimarySourceWaiverSchema, FreshnessRequirementsSchema, ReviewProfilePresetSchema, DEFAULT_REVIEW_PROFILES, ResearchYamlSchema;
|
|
114
115
|
var init_schema = __esm({
|
|
115
116
|
"src/intake/schema.ts"() {
|
|
116
117
|
"use strict";
|
|
@@ -160,10 +161,20 @@ var init_schema = __esm({
|
|
|
160
161
|
contradiction: ContradictionGateSchema.default({}),
|
|
161
162
|
section_budget: SectionBudgetGateSchema.default({})
|
|
162
163
|
});
|
|
164
|
+
SectionScopedWaiverSchema = z.object({
|
|
165
|
+
section_id: z.string().regex(/^[0-9]{2}-[a-z0-9-]+$/, 'Section id must look like "01-landscape"'),
|
|
166
|
+
scope: z.enum(["min_independent_publishers", "primary_sources_required"]),
|
|
167
|
+
reason: z.string().min(1),
|
|
168
|
+
compensating_controls: z.array(z.string()).min(1)
|
|
169
|
+
});
|
|
163
170
|
PrimarySourceWaiverSchema = z.object({
|
|
164
171
|
status: z.enum(["none", "requested", "granted"]).default("none"),
|
|
165
172
|
reason: z.string().optional(),
|
|
166
|
-
compensating_controls: z.array(z.string()).default([])
|
|
173
|
+
compensating_controls: z.array(z.string()).default([]),
|
|
174
|
+
// Section-scoped waivers; each entry is its own waiver record. Independent
|
|
175
|
+
// of the pack-level status/reason/compensating_controls fields above.
|
|
176
|
+
// Defaults to [] for backward compatibility — existing packs unaffected.
|
|
177
|
+
section_waivers: z.array(SectionScopedWaiverSchema).default([])
|
|
167
178
|
});
|
|
168
179
|
FreshnessRequirementsSchema = z.object({
|
|
169
180
|
required: z.boolean().default(true),
|
|
@@ -4207,9 +4218,28 @@ function applyWaivers(input, results) {
|
|
|
4207
4218
|
const updated = results.map((r) => ({ ...r }));
|
|
4208
4219
|
const applied = [];
|
|
4209
4220
|
const validationFailures = [];
|
|
4210
|
-
|
|
4211
|
-
|
|
4221
|
+
applyPackLevelWaiver({
|
|
4222
|
+
waiver,
|
|
4223
|
+
cfg,
|
|
4224
|
+
updated,
|
|
4225
|
+
applied,
|
|
4226
|
+
validationFailures
|
|
4227
|
+
});
|
|
4228
|
+
for (const sw of waiver.section_waivers ?? []) {
|
|
4229
|
+
if (sw.section_id !== input.section.id) continue;
|
|
4230
|
+
applySectionScopedWaiver({
|
|
4231
|
+
waiver: sw,
|
|
4232
|
+
cfg,
|
|
4233
|
+
updated,
|
|
4234
|
+
applied,
|
|
4235
|
+
validationFailures
|
|
4236
|
+
});
|
|
4212
4237
|
}
|
|
4238
|
+
return { updatedResults: updated, waivers_applied: applied, waiver_validation_failures: validationFailures };
|
|
4239
|
+
}
|
|
4240
|
+
function applyPackLevelWaiver(args) {
|
|
4241
|
+
const { waiver, cfg, updated, applied, validationFailures } = args;
|
|
4242
|
+
if (waiver.status !== "granted") return;
|
|
4213
4243
|
if (!waiver.reason || waiver.reason.trim().length === 0) {
|
|
4214
4244
|
validationFailures.push({
|
|
4215
4245
|
family: "waivers",
|
|
@@ -4219,7 +4249,7 @@ function applyWaivers(input, results) {
|
|
|
4219
4249
|
evidence: [],
|
|
4220
4250
|
blocks_synthesis: true
|
|
4221
4251
|
});
|
|
4222
|
-
return
|
|
4252
|
+
return;
|
|
4223
4253
|
}
|
|
4224
4254
|
if (waiver.compensating_controls.length === 0) {
|
|
4225
4255
|
validationFailures.push({
|
|
@@ -4230,7 +4260,7 @@ function applyWaivers(input, results) {
|
|
|
4230
4260
|
evidence: [],
|
|
4231
4261
|
blocks_synthesis: true
|
|
4232
4262
|
});
|
|
4233
|
-
return
|
|
4263
|
+
return;
|
|
4234
4264
|
}
|
|
4235
4265
|
if (!cfg.primary_source_waiver_allowed) {
|
|
4236
4266
|
validationFailures.push({
|
|
@@ -4241,7 +4271,7 @@ function applyWaivers(input, results) {
|
|
|
4241
4271
|
evidence: [],
|
|
4242
4272
|
blocks_synthesis: true
|
|
4243
4273
|
});
|
|
4244
|
-
return
|
|
4274
|
+
return;
|
|
4245
4275
|
}
|
|
4246
4276
|
for (let i = 0; i < updated.length; i += 1) {
|
|
4247
4277
|
const r = updated[i];
|
|
@@ -4263,7 +4293,62 @@ function applyWaivers(input, results) {
|
|
|
4263
4293
|
});
|
|
4264
4294
|
}
|
|
4265
4295
|
}
|
|
4266
|
-
|
|
4296
|
+
}
|
|
4297
|
+
function applySectionScopedWaiver(args) {
|
|
4298
|
+
const { waiver, cfg, updated, applied, validationFailures } = args;
|
|
4299
|
+
if (!waiver.reason || waiver.reason.trim().length === 0) {
|
|
4300
|
+
validationFailures.push({
|
|
4301
|
+
family: "waivers",
|
|
4302
|
+
check: "section_scoped_waiver_reason_required",
|
|
4303
|
+
status: "fail",
|
|
4304
|
+
detail: `Section-scoped waiver for ${waiver.section_id}/${waiver.scope} is missing a reason. Waiver is invalid; original failure stands.`,
|
|
4305
|
+
evidence: [waiver.section_id],
|
|
4306
|
+
blocks_synthesis: true
|
|
4307
|
+
});
|
|
4308
|
+
return;
|
|
4309
|
+
}
|
|
4310
|
+
if (waiver.compensating_controls.length === 0) {
|
|
4311
|
+
validationFailures.push({
|
|
4312
|
+
family: "waivers",
|
|
4313
|
+
check: "section_scoped_waiver_compensating_controls_required",
|
|
4314
|
+
status: "fail",
|
|
4315
|
+
detail: `Section-scoped waiver for ${waiver.section_id}/${waiver.scope} has empty compensating_controls. Waiver is invalid; original failure stands.`,
|
|
4316
|
+
evidence: [waiver.section_id],
|
|
4317
|
+
blocks_synthesis: true
|
|
4318
|
+
});
|
|
4319
|
+
return;
|
|
4320
|
+
}
|
|
4321
|
+
if (!cfg.primary_source_waiver_allowed) {
|
|
4322
|
+
validationFailures.push({
|
|
4323
|
+
family: "waivers",
|
|
4324
|
+
check: "section_scoped_waiver_allowed_by_pack",
|
|
4325
|
+
status: "fail",
|
|
4326
|
+
detail: `Pack policy gates.source_floor.primary_source_waiver_allowed=false; section-scoped waiver for ${waiver.section_id}/${waiver.scope} cannot be applied.`,
|
|
4327
|
+
evidence: [waiver.section_id],
|
|
4328
|
+
blocks_synthesis: true
|
|
4329
|
+
});
|
|
4330
|
+
return;
|
|
4331
|
+
}
|
|
4332
|
+
for (let i = 0; i < updated.length; i += 1) {
|
|
4333
|
+
const r = updated[i];
|
|
4334
|
+
if (r.family === "source_floor" && r.check === waiver.scope && r.status === "fail") {
|
|
4335
|
+
const original = r.status;
|
|
4336
|
+
updated[i] = {
|
|
4337
|
+
...r,
|
|
4338
|
+
status: "pass_with_waiver",
|
|
4339
|
+
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.`,
|
|
4340
|
+
blocks_synthesis: false
|
|
4341
|
+
};
|
|
4342
|
+
applied.push({
|
|
4343
|
+
family: "source_floor",
|
|
4344
|
+
check: waiver.scope,
|
|
4345
|
+
reason: waiver.reason,
|
|
4346
|
+
compensating_controls: waiver.compensating_controls,
|
|
4347
|
+
original_status: original,
|
|
4348
|
+
new_status: "pass_with_waiver"
|
|
4349
|
+
});
|
|
4350
|
+
}
|
|
4351
|
+
}
|
|
4267
4352
|
}
|
|
4268
4353
|
var init_waivers = __esm({
|
|
4269
4354
|
"src/gates/checks/waivers.ts"() {
|
|
@@ -5520,9 +5605,11 @@ function pickHighestPriority(decisions) {
|
|
|
5520
5605
|
return "accepted_for_synthesis";
|
|
5521
5606
|
}
|
|
5522
5607
|
function deriveClaimReviews(args) {
|
|
5523
|
-
const { claims, findings, reviewer, reviewMethod } = args;
|
|
5608
|
+
const { claims, findings, reviewer, reviewMethod, activeSectionWaivers } = args;
|
|
5524
5609
|
const reviews = [];
|
|
5525
5610
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5611
|
+
const monopolyWaived = Array.isArray(activeSectionWaivers) && activeSectionWaivers.some((w) => w.scope === "min_independent_publishers");
|
|
5612
|
+
const isWaivedFinding = (f) => monopolyWaived && f.category === "source_cluster_monopoly";
|
|
5526
5613
|
for (const claim of claims) {
|
|
5527
5614
|
const claimFindings = findings.filter((f) => f.claim_ids.includes(claim.claim_id));
|
|
5528
5615
|
if (claimFindings.length === 0) {
|
|
@@ -5539,6 +5626,7 @@ function deriveClaimReviews(args) {
|
|
|
5539
5626
|
}
|
|
5540
5627
|
let decisions = [];
|
|
5541
5628
|
for (const f of claimFindings) {
|
|
5629
|
+
if (isWaivedFinding(f)) continue;
|
|
5542
5630
|
if (f.severity === "block") {
|
|
5543
5631
|
decisions.push(BLOCK_TO_DECISION[f.category] ?? "rejected");
|
|
5544
5632
|
} else if (f.severity === "warn") {
|
|
@@ -5555,7 +5643,9 @@ function deriveClaimReviews(args) {
|
|
|
5555
5643
|
);
|
|
5556
5644
|
}
|
|
5557
5645
|
const decision = pickHighestPriority(decisions);
|
|
5558
|
-
const reasonParts = claimFindings.filter((f) => f.severity !== "info").map(
|
|
5646
|
+
const reasonParts = claimFindings.filter((f) => f.severity !== "info").map(
|
|
5647
|
+
(f) => isWaivedFinding(f) ? `${f.category} (${f.severity}, waived)` : `${f.category} (${f.severity})`
|
|
5648
|
+
);
|
|
5559
5649
|
const reason = reasonParts.length > 0 ? `Findings: ${reasonParts.join("; ")}.` : "Only info-level findings; accepted.";
|
|
5560
5650
|
reviews.push({
|
|
5561
5651
|
claim_id: claim.claim_id,
|
|
@@ -5936,7 +6026,8 @@ async function review(options) {
|
|
|
5936
6026
|
candidateClaims,
|
|
5937
6027
|
drafts: acceptedDrafts,
|
|
5938
6028
|
llmFindingsRejected,
|
|
5939
|
-
profile: options.profile ?? DEFAULT_PROFILE
|
|
6029
|
+
profile: options.profile ?? DEFAULT_PROFILE,
|
|
6030
|
+
research
|
|
5940
6031
|
});
|
|
5941
6032
|
}
|
|
5942
6033
|
async function runMultiPassReview(args) {
|
|
@@ -5998,7 +6089,8 @@ async function runMultiPassReview(args) {
|
|
|
5998
6089
|
candidateClaims: args.candidateClaims,
|
|
5999
6090
|
drafts: merged,
|
|
6000
6091
|
llmFindingsRejected,
|
|
6001
|
-
profile: args.options.profile ?? DEFAULT_PROFILE
|
|
6092
|
+
profile: args.options.profile ?? DEFAULT_PROFILE,
|
|
6093
|
+
research: args.research
|
|
6002
6094
|
});
|
|
6003
6095
|
}
|
|
6004
6096
|
async function reviewWithSpecificReviewer(args) {
|
|
@@ -6026,7 +6118,8 @@ async function reviewWithSpecificReviewer(args) {
|
|
|
6026
6118
|
candidateClaims: args.candidateClaims,
|
|
6027
6119
|
drafts: result.drafts,
|
|
6028
6120
|
llmFindingsRejected: 0,
|
|
6029
|
-
profile: args.options.profile ?? DEFAULT_PROFILE
|
|
6121
|
+
profile: args.options.profile ?? DEFAULT_PROFILE,
|
|
6122
|
+
research: args.research
|
|
6030
6123
|
});
|
|
6031
6124
|
}
|
|
6032
6125
|
async function finalizeReview(args) {
|
|
@@ -6049,11 +6142,15 @@ async function finalizeReview(args) {
|
|
|
6049
6142
|
seen.add(f.finding_id);
|
|
6050
6143
|
dedupedFindings.push(f);
|
|
6051
6144
|
}
|
|
6145
|
+
const activeSectionWaivers = args.research.primary_source_waiver.section_waivers.filter(
|
|
6146
|
+
(w) => w.section_id === args.sectionId
|
|
6147
|
+
);
|
|
6052
6148
|
const claimReviews = deriveClaimReviews({
|
|
6053
6149
|
claims: args.candidateClaims,
|
|
6054
6150
|
findings: dedupedFindings,
|
|
6055
6151
|
reviewer: args.reviewer,
|
|
6056
|
-
reviewMethod: args.reviewMethod
|
|
6152
|
+
reviewMethod: args.reviewMethod,
|
|
6153
|
+
activeSectionWaivers
|
|
6057
6154
|
});
|
|
6058
6155
|
const decisionCounts = {
|
|
6059
6156
|
accepted_for_synthesis: 0,
|
|
@@ -8780,6 +8877,19 @@ function buildStaleSources(input) {
|
|
|
8780
8877
|
}
|
|
8781
8878
|
return out;
|
|
8782
8879
|
}
|
|
8880
|
+
function findSectionWaiver(research, sectionId, scope) {
|
|
8881
|
+
return (research.primary_source_waiver.section_waivers ?? []).find(
|
|
8882
|
+
(w) => w.section_id === sectionId && w.scope === scope
|
|
8883
|
+
);
|
|
8884
|
+
}
|
|
8885
|
+
function annotateWeakSource(row, waiver) {
|
|
8886
|
+
if (!waiver) return row;
|
|
8887
|
+
return { ...row, waived: true, waiver_reason: waiver.reason };
|
|
8888
|
+
}
|
|
8889
|
+
function annotateDiversityGap(row, waiver) {
|
|
8890
|
+
if (!waiver) return row;
|
|
8891
|
+
return { ...row, waived: true, waiver_reason: waiver.reason };
|
|
8892
|
+
}
|
|
8783
8893
|
function buildWeakSources(input) {
|
|
8784
8894
|
const out = [];
|
|
8785
8895
|
const cfg = input.research.gates.source_floor;
|
|
@@ -8789,33 +8899,50 @@ function buildWeakSources(input) {
|
|
|
8789
8899
|
const publishers = new Set(
|
|
8790
8900
|
sectionSources.map((c) => c.publisher).filter((p) => typeof p === "string")
|
|
8791
8901
|
);
|
|
8902
|
+
const monopolyWaiver = findSectionWaiver(input.research, sid, "min_independent_publishers");
|
|
8903
|
+
const primaryWaiver = findSectionWaiver(input.research, sid, "primary_sources_required");
|
|
8792
8904
|
if (publishers.size === 1 && sectionSources.length >= 2) {
|
|
8793
|
-
out.push(
|
|
8794
|
-
|
|
8795
|
-
|
|
8796
|
-
|
|
8797
|
-
|
|
8798
|
-
|
|
8799
|
-
|
|
8905
|
+
out.push(
|
|
8906
|
+
annotateWeakSource(
|
|
8907
|
+
{
|
|
8908
|
+
reason: "source_cluster_monopoly",
|
|
8909
|
+
section_id: sid,
|
|
8910
|
+
details: `Every source in this section traces to a single publisher (${[...publishers][0]}).`,
|
|
8911
|
+
evidence_ids: sectionSources.map((s) => s.source_id),
|
|
8912
|
+
artifact_path: `sections/${sid}/sources.jsonl`
|
|
8913
|
+
},
|
|
8914
|
+
monopolyWaiver
|
|
8915
|
+
)
|
|
8916
|
+
);
|
|
8800
8917
|
}
|
|
8801
8918
|
if (publishers.size < cfg.min_independent_publishers) {
|
|
8802
|
-
out.push(
|
|
8803
|
-
|
|
8804
|
-
|
|
8805
|
-
|
|
8806
|
-
|
|
8807
|
-
|
|
8808
|
-
|
|
8919
|
+
out.push(
|
|
8920
|
+
annotateWeakSource(
|
|
8921
|
+
{
|
|
8922
|
+
reason: "low_independent_publishers",
|
|
8923
|
+
section_id: sid,
|
|
8924
|
+
details: `${publishers.size} independent publisher(s) \u2014 pack policy requires at least ${cfg.min_independent_publishers}.`,
|
|
8925
|
+
evidence_ids: [...publishers],
|
|
8926
|
+
artifact_path: `sections/${sid}/sources.jsonl`
|
|
8927
|
+
},
|
|
8928
|
+
monopolyWaiver
|
|
8929
|
+
)
|
|
8930
|
+
);
|
|
8809
8931
|
}
|
|
8810
8932
|
const primary = sectionSources.filter((c) => c.source_type === "primary").length;
|
|
8811
8933
|
if (primary < cfg.primary_sources_required) {
|
|
8812
|
-
out.push(
|
|
8813
|
-
|
|
8814
|
-
|
|
8815
|
-
|
|
8816
|
-
|
|
8817
|
-
|
|
8818
|
-
|
|
8934
|
+
out.push(
|
|
8935
|
+
annotateWeakSource(
|
|
8936
|
+
{
|
|
8937
|
+
reason: "missing_primary_source",
|
|
8938
|
+
section_id: sid,
|
|
8939
|
+
details: `${primary} primary source(s) \u2014 pack policy requires at least ${cfg.primary_sources_required}.`,
|
|
8940
|
+
evidence_ids: sectionSources.filter((c) => c.source_type === "primary").map((c) => c.source_id),
|
|
8941
|
+
artifact_path: `sections/${sid}/sources.jsonl`
|
|
8942
|
+
},
|
|
8943
|
+
primaryWaiver
|
|
8944
|
+
)
|
|
8945
|
+
);
|
|
8819
8946
|
}
|
|
8820
8947
|
const types = /* @__PURE__ */ new Map();
|
|
8821
8948
|
for (const c of sectionSources) types.set(c.source_type, (types.get(c.source_type) ?? 0) + 1);
|
|
@@ -8930,20 +9057,31 @@ function buildSourceDiversityGaps(input) {
|
|
|
8930
9057
|
});
|
|
8931
9058
|
continue;
|
|
8932
9059
|
}
|
|
9060
|
+
const monopolyWaiver = findSectionWaiver(input.research, sid, "min_independent_publishers");
|
|
8933
9061
|
if (publishers.size === 1 && sectionSources.length >= 2) {
|
|
8934
|
-
out.push(
|
|
8935
|
-
|
|
8936
|
-
|
|
8937
|
-
|
|
8938
|
-
|
|
8939
|
-
|
|
9062
|
+
out.push(
|
|
9063
|
+
annotateDiversityGap(
|
|
9064
|
+
{
|
|
9065
|
+
reason: "section_publisher_monopoly",
|
|
9066
|
+
section_id: sid,
|
|
9067
|
+
details: `Section sources monopolized by ${[...publishers][0]}.`,
|
|
9068
|
+
evidence_ids: sectionSources.map((s) => s.source_id)
|
|
9069
|
+
},
|
|
9070
|
+
monopolyWaiver
|
|
9071
|
+
)
|
|
9072
|
+
);
|
|
8940
9073
|
} else if (publishers.size < cfg.min_independent_publishers) {
|
|
8941
|
-
out.push(
|
|
8942
|
-
|
|
8943
|
-
|
|
8944
|
-
|
|
8945
|
-
|
|
8946
|
-
|
|
9074
|
+
out.push(
|
|
9075
|
+
annotateDiversityGap(
|
|
9076
|
+
{
|
|
9077
|
+
reason: "low_section_publisher_count",
|
|
9078
|
+
section_id: sid,
|
|
9079
|
+
details: `${publishers.size} publisher(s); pack policy requires ${cfg.min_independent_publishers}.`,
|
|
9080
|
+
evidence_ids: [...publishers]
|
|
9081
|
+
},
|
|
9082
|
+
monopolyWaiver
|
|
9083
|
+
)
|
|
9084
|
+
);
|
|
8947
9085
|
}
|
|
8948
9086
|
}
|
|
8949
9087
|
const pubSectionCount = /* @__PURE__ */ new Map();
|
|
@@ -11734,7 +11872,7 @@ var init_src = __esm({
|
|
|
11734
11872
|
init_triage();
|
|
11735
11873
|
init_discover();
|
|
11736
11874
|
init_errors();
|
|
11737
|
-
RESEARCH_OS_VERSION = "0.3.
|
|
11875
|
+
RESEARCH_OS_VERSION = "0.3.1";
|
|
11738
11876
|
}
|
|
11739
11877
|
});
|
|
11740
11878
|
|