diff --git a/src/index.ts b/src/index.ts index 865d213..be831e0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -33,7 +33,7 @@ import { import { handleMcp } from "./mcp.js"; import { SOURCES } from "./sources.js"; import { syncAll } from "./sync.js"; -import { renderDashboard, renderOpportunities, renderOpportunity, renderStories, renderStory, renderTrustPolicy } from "./web.js"; +import { renderAboutNewsroom, renderDashboard, renderOpportunities, renderOpportunity, renderStories, renderStory, renderTrustPolicy } from "./web.js"; const app = express(); app.set("trust proxy", true); @@ -284,6 +284,22 @@ app.post("/opportunities/:id/status", requireWebAuth, requireWebRole("editor"), return res.redirect(req.body.returnTo === "detail" ? `/opportunities/${encodeURIComponent(id)}` : "/opportunities"); }); +app.get("/about", requireWebAuth, async (req, res) => { + const [sources, trustEntries] = await Promise.all([ + pool.query("SELECT * FROM sources ORDER BY id"), + listSourceTrustPolicy() + ]); + const user = (req as any).user ?? {}; + const identity = String(user.name || user.preferred_username || user.email || "Signed-in user"); + const role = webMediaRole(req); + return res.type("html").send(renderAboutNewsroom({ + identity, + role, + sources: sources.rows, + trustEntries + })); +}); + app.get("/trust", requireWebAuth, async (req, res) => { const entries = await listSourceTrustPolicy(); const user = (req as any).user ?? {}; diff --git a/src/web.ts b/src/web.ts index 68addd1..10c02ad 100644 --- a/src/web.ts +++ b/src/web.ts @@ -62,7 +62,7 @@ function canRole(role: string | undefined, required: "viewer" | "editor" | "admi function shell(args: { title: string; - active: "dashboard" | "stories" | "opportunities" | "trust"; + active: "dashboard" | "stories" | "opportunities" | "trust" | "about"; identity: string; role?: string; body: string; @@ -71,7 +71,8 @@ function shell(args: { ["dashboard", "/", "Desk"], ["stories", "/stories", "Story Threads"], ["opportunities", "/opportunities", "Opportunities"], - ["trust", "/trust", "Source Trust"] + ["trust", "/trust", "Source Trust"], + ["about", "/about", "About"] ] as const; return ` @@ -198,6 +199,23 @@ button,input,select{font:inherit} .review-irrelevant{opacity:.52} .review-resolved{border-style:dashed} .readonly-note{padding:11px 13px;background:#f7f9fb;border:1px solid #e4e9ef;border-radius:9px;color:#5d6879;font-size:.84rem} +.about-hero{background:linear-gradient(135deg,#111827,#1f2937);color:#fff;border-radius:18px;padding:30px;box-shadow:var(--shadow)} +.about-hero .eyebrow{color:#aeb8c7}.about-hero p{color:#d7dde7;max-width:900px;margin:10px 0 0} +.about-toc{display:flex;gap:8px;flex-wrap:wrap;margin-top:18px}.about-toc a{color:#dbe7ff;text-decoration:none;border:1px solid rgba(255,255,255,.16);border-radius:999px;padding:6px 10px;font-size:.8rem} +.about-flow{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:8px} +.flow-step{position:relative;padding:15px 13px;background:#fff;border:1px solid var(--line);border-radius:12px;min-height:118px} +.flow-step:not(:last-child)::after{content:"→";position:absolute;right:-8px;top:42px;z-index:2;color:#8792a4;font-weight:900} +.flow-num{font-size:.7rem;color:var(--muted);font-weight:800;text-transform:uppercase;letter-spacing:.08em}.flow-title{font-weight:780;margin-top:6px}.flow-copy{font-size:.8rem;color:var(--muted);margin-top:5px} +.about-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px} +.about-grid-3{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px} +.about-section{scroll-margin-top:82px} +.about-section h2{font-size:1.25rem;margin:0 0 7px;letter-spacing:-.02em}.about-section>p{color:var(--muted);max-width:980px} +.about-list{margin:0;padding-left:19px;color:#465268}.about-list li+li{margin-top:6px} +.about-table{width:100%;border-collapse:collapse;font-size:.84rem}.about-table th,.about-table td{text-align:left;padding:10px;border-bottom:1px solid #edf0f4;vertical-align:top}.about-table th{font-size:.74rem;text-transform:uppercase;letter-spacing:.06em;color:var(--muted)} +.principle{padding:18px}.principle h3{margin:0 0 6px;font-size:.96rem}.principle p{margin:0;color:var(--muted);font-size:.86rem} +.codeflow{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;background:#111827;color:#e5e7eb;border-radius:12px;padding:16px;overflow:auto;white-space:pre;line-height:1.65;font-size:.8rem} +@media(max-width:1100px){.about-flow{grid-template-columns:repeat(3,minmax(0,1fr))}.flow-step::after{display:none}.about-grid-3{grid-template-columns:1fr 1fr}} +@media(max-width:760px){.about-flow,.about-grid,.about-grid-3{grid-template-columns:1fr}.about-hero{padding:22px}} @media(max-width:850px){.workbench{grid-template-columns:1fr}} .trust-form{display:grid;grid-template-columns:1.2fr 1.2fr .6fr 1fr 1fr;gap:10px;align-items:end} .trust-form .wide{grid-column:1/-1} @@ -525,3 +543,185 @@ export function renderTrustPolicy(args:{identity:string;entries:AnyRow[];role?:s `; return shell({title:"Source Trust",active:"trust",identity:args.identity,role:args.role,body}); } + + +export function renderAboutNewsroom(args:{ + identity:string; + role?:string; + sources:AnyRow[]; + trustEntries:AnyRow[]; +}) { + const enabledSources = args.sources.filter((source:AnyRow)=>source.enabled !== false); + const healthySources = enabledSources.filter((source:AnyRow)=>!source.last_error); + const sourceClasses = Array.from(new Set(enabledSources.map((source:AnyRow)=>String(source.source_class||"other")))).sort(); + const trustEnabled = args.trustEntries.filter((entry:AnyRow)=>entry.enabled !== false); + + const body = ` +
+
About the newsroom
+

How Media Intelligence works

+

This newsroom is a research and editorial operating system. It continuously watches configured public sources, preserves evidence, links claims to provenance, asks agents to look for contradictory or independent evidence, and then hands the result to people for editorial decisions. It is designed to make research faster without hiding where a claim came from or automating the final editorial judgement.

+
+ WorkflowSourcesTrust & independenceEvidenceStory ThreadsAgentsHuman controlRolesAuditabilityLimits +
+
+ +
+

Newsroom workflow

The system moves from detection to evidence to a human editorial decision.

+
+
01
Monitor
Configured feeds, data sources, notices and official pages are checked on schedule.
+
02
Detect
New or changed items become signals with source, timestamp and raw payload metadata.
+
03
Triage
Scoring highlights items that match editorial watch terms or source-specific importance rules.
+
04
Research
Story Threads connect evidence, claims, counterclaims, questions, trends and relationships.
+
05
Challenge
Agents and editors seek independent corroboration and evidence that weakens the first interpretation.
+
06
Decide
People decide newsworthiness, framing, final message, recipients and whether anything is sent or published.
+
+
+ +
+

1. Source monitoring

The source layer is intentionally mixed: primary records, official datasets, notices, analysis, market data and corporate first-party material can all be monitored, but they are not treated as equivalent evidence.

+
+
${compact(enabledSources.length)}
enabled sources
+
${compact(healthySources.length)}
currently without sync errors
+
${compact(sourceClasses.length)}
source classes
+
${compact(trustEnabled.length)}
enabled trust-policy entries
+
+
+ + + + + + + + + +
ClassWhat it meansTypical use
official_primaryPrimary institutional record or publication.What an institution formally published, filed or stated.
official_dataFirst-party statistical or operational data.Metrics, series, releases and machine-readable evidence.
official_registryAuthoritative registry records.Companies, registrations, removals and formal status.
official_noticeLegally or formally published notices.Gazette notices, market announcements and similar events.
official_analysisAnalysis published by an authoritative institution.Forecasts, commentary and analytical reports.
official_market_notice / official_market_dataExchange announcements or direct market observations.Company disclosures, price/volume context and market events.
corporate_primaryA company speaking for itself.Strong evidence of what the company announced; not independent validation of its claims.
independent_research / newsroom / specialistExternal analysis and reporting.Independent corroboration, criticism, interpretation and subject expertise.
+
+
+ +
+

2. Trust weights and independence

A trust weight is an editorial prior about a source, not a probability that the source is telling the truth.

+
+

Trust weight

Each configured domain can carry a 0–1 editorial weight. The weight represents how useful and authoritative that source generally is for the type of evidence it publishes. Editors can change these weights in Source Trust.

+

Independence group

Multiple URLs owned by the same publisher belong to one independence group. Ten articles from one organisation therefore do not become ten independent confirmations.

+

Primary ≠ independent

A company newsroom is excellent evidence that the company made an announcement. It is not independent evidence that the company's performance, safety or impact claims are correct.

+

Corroboration bonus

Agents evaluate the strongest source in each independence group. Additional genuinely independent groups can strengthen a source set, while duplicates inside one group do not inflate confidence.

+
+
OpenAI announcement ─┐ + ├─ independence group: openai +OpenAI documentation ─┘ + +Independent benchmark ─── independence group: benchmark-provider +University research ──── independence group: university-x + +Result: three documents, but three independent groups only if their underlying evidence is genuinely independent.
+
+ +
+

3. Evidence preservation

The newsroom distinguishes discovery metadata from substantive evidence so an attractive headline cannot masquerade as a verified source.

+
+

Discovery metadata

A title, sitemap entry or listing tells the newsroom that something exists. It is useful for discovery, but usually does not satisfy the sourcing gate.

+

Source payload

First-party RSS, APIs and structured payloads can be preserved directly when they contain enough substantive content and provenance.

+

Source document

The strongest class in the current system: the actual page or document is fetched, text is extracted, hashed, timestamped and frozen as evidence.

+
+
+

Why freeze evidence? Web pages change. A Story Thread should be able to show what the newsroom actually saw when a claim was researched, including the source URL, retrieval time, content hash, excerpt and snapshot identity.

+

The original URL remains available for readers, but the frozen snapshot is the provenance record used by the research graph.

+
+
+ +
+

4. Story Threads and 360° research

A Story Thread is not an article draft. It is the research object from which an editorial decision can later be made.

+
+

Graph model

A thread can contain evidence, claims, counterclaims, trends, forecasts, actuals, questions, context, organisations, people, places and metrics. Relationships state why one node supports, contradicts, qualifies, updates or investigates another.

+

360° rule

For every apparent story, the newsroom asks: what does this appear to say, and what is the strongest evidence that this interpretation is incomplete or wrong? Counter-evidence is a required research pass, not an optional decoration.

+
+
Primary signal + ↓ +Initial interpretation + ↓ +Direct supporting evidence + ↓ +Counter-evidence / alternative explanation + ↓ +Independent source groups + ↓ +Historical / regional / industry context + ↓ +Unresolved questions + ↓ +Sourcing gate + ↓ +Human editorial review
+
+ +
+

5. What agents do

Agents accelerate research and maintenance. They do not own the editorial decision.

+
+

Good agent work

Monitor sources, gather evidence, freeze documents, identify entities, compare statistics, find contradictory evidence, propose relationships, audit sourcing gaps and surface source-health problems.

+

MCP tools

Agents use the Media Intelligence MCP to search evidence, inspect source health, evaluate trust and independence, create or extend Story Threads, and post research findings into Agent Activity.

+

Transparent output

Research actions are attached to identifiable sources, nodes, messages and audit events so a human can inspect why an agent reached a research state.

+
+
+ +
+

6. Human editorial control

The system is intentionally built around explicit human gates.

+
+ + + + + + +
DecisionWho owns itSystem role
Is this newsworthy?Human editorAgents can surface evidence and opportunity signals.
What is the message or framing?Human editorAgents can show competing interpretations and factual constraints.
Is the evidence sufficient?Human editorThe sourcing gate makes gaps visible and blocks premature workflow progression.
Who should receive it?Human editorFuture contact intelligence can propose audiences, not silently choose them.
Send or publish?Human editorNo autonomous publishing or distribution is assumed by the newsroom workflow.
+
+
+ +
+

7. Roles and permissions

The same permission model applies to the website and MCP actions.

+
+

Viewer

Read Story Threads, inspect evidence and provenance, browse opportunities, view Source Trust and use read-only research tools.

+

Editor

Viewer access plus research mutations, Story Thread workbench actions, triage, status changes, evidence enrichment and Agent Activity actions.

+

Admin

Editor access plus newsroom policy/configuration actions such as editing the Source Trust policy. Unrecognised authenticated roles fail safe to viewer.

+
+
+ +
+

8. Sourcing gate and auditability

A thread cannot become editorially ready just because an agent sounds confident.

+
+

Current sourcing checks

    +
  • At least two independent source groups.
  • +
  • At least two substantive evidence-backed nodes.
  • +
  • At least one Tier 1 source.
  • +
  • Active assertions connected directly to qualifying evidence.
  • +
  • No source-label-only node pretending to be direct evidence.
  • +
  • A completed counter-evidence pass before review/develop.
  • +
+

Audit trail

    +
  • Signal ingestion and change detection are recorded.
  • +
  • Evidence snapshots have hashes and retrieval timestamps.
  • +
  • Story nodes and relationships record their creator.
  • +
  • Editorial status changes are recorded.
  • +
  • Agent messages and acknowledgements remain visible.
  • +
  • Source failures and recoveries appear in newsroom activity.
  • +
+
+
+ +
+

9. What the newsroom does not claim

Automation helps organise evidence; it does not remove uncertainty.

+
+

A weight is not truth

A highly trusted source can still contain errors. A low-weight source can still publish an important fact. Weights guide research priority and corroboration, not verdicts.

+

Independent publishers can share one upstream source

Two newspapers repeating one press release are not genuinely independent evidence. Agents are instructed to check shared upstream provenance.

+

Source health is imperfect

WAFs, blocked feeds, changed HTML, missing APIs and licensing restrictions can make sources temporarily unavailable. The Desk surfaces source failures instead of silently hiding them.

+

Research-ready is not publication-ready

Passing the sourcing gate means the configured research requirements are met. It does not replace editorial judgement, legal review, subject-matter expertise or final fact checking.

+
+
+ +
+
Operating principle: the newsroom should make it easy to answer two questions for any important statement: “What is the evidence?” and “What would make us change our mind?”
+
`; + + return shell({title:"About the Newsroom",active:"about",identity:args.identity,role:args.role,body}); +}