Release: V1.2. Veritas is not part of V1.0. Everything on this page stays in the tree and stays worth building — the room has to be stocked and reviewed before it opens — but no V1.0 console may advertise, link to, count, or compose it. V1.0 opens Tara, Nyx, Arete, and Nisaba; see
V1/BRAND.md.
Veritas is the grounded story, claim, evidence, source, and explainer domain of Oshun V1 — the place a reader meets the news and the empirical record with the sourcing attached: every claim carries a confidence band, every source carries a quality band and a reviewer attestation, every competing view is surfaced side by side rather than silently hidden, and when a source later retracts, the correction cascades to every dependent surface the reader ever saw. It serves the reader who wants to know not just what is claimed but how strongly it is supported and by whom. Veritas sits inside the single Oshun shell alongside the contemplative domain (Tara — Rituals and Contemplative Practice), the goals domain (Arete — Goals, Habits, and Reflection), the scholarly-study domain (Nisaba — Scholarly Study), the education domain (Metis — Education and Tutoring), and the assistant.
This page is a product-and-code reading of Veritas. The canonical domain logic
is exceptionally code-complete — the source-quality composite, the editorial
state machine, the attestor workflow, the counterclaim balance rules, the
topic-hub composer, the retraction-cascade UX, and the contradiction probe are
all real, deterministic, and contract-enforced. What remains aspirational is
called out honestly throughout: per-tenant composite tightening, weekly drift
re-computation, credential round-trip proof artifacts, reader quiet-hours, and
the fully wired end-to-end retraction journey (the ports exist; the live
fan-out is at the deployable boundary). The completion state of every item lives
in ../TODOS.md; cross-domain build ordering lives in
../DEPENDENCIES.md.
Two libraries, two adapters, ~64 cousins — read this first#
Veritas is one domain name attached to several distinct code locations, and
mixing them up is the single biggest source of confusion. The canonical V1
domain is @oshun/domain-veritas at libs/oshun/domain-veritas. Its
event-bus companion is @oshun/veritas-cascade-worker at
libs/oshun/veritas-cascade-worker. The canonical contracts live at
libs/contracts/src/veritas and are imported as @oshun/contracts/veritas
through the workspace path map.
There is also a separate, much larger collection at libs/veritas/ — about
66 sub-libraries (agents-fact-checking, agents-journalism,
bias-detection, claims, knowledge-graph, headline-service, nlp-core,
rag, recommendations, and so on). That collection is not the canonical V1
Veritas domain; it is a distinct body of fact-checking and journalism tooling.
When this page says "Veritas," it means @oshun/domain-veritas unless a
libs/veritas/* path is written out explicitly.
A wiring caveat worth recording:
libs/contracts/veritas/(nosrc) contains only a.gitkeepfile — nopackage.json, no exports. It is not a standalone published package. Veritas types are imported as@oshun/contracts/veritas, which resolves tolibs/contracts/src/veritasthrough the path map. Earlier docs described a "standalonelibs/contracts/veritaspackage for consumers"; that package does not exist.
The two-tier adapter architecture#
@oshun/domain-veritas ships two models side by side, and the docs have
historically described only one of them.
-
The presentational article-feed adapter (
types.ts). This is the reader-facing news app surface: aVeritasApiAdapterwithgetTrendingArticles,getArticleBrief,getContinueReading,getTopClaims,getClaimDetail,getTrendingTopics,getCategories,getSavedArticles,saveArticle/unsaveArticle,getFollowedTopics,followTopic/unfollowTopic, andgetHealth. Its vocabulary is presentational:VeritasVerdict(8 values —verified,likely_true,disputed,misleading,mostly_false,false,unverifiable,unverified),VeritasCredibilityTier(high·medium·low·unknown),VeritasClaimType(7 values —factual,opinion,prediction,quote,statistical,historical,scientific), andVeritasArticleContentType/VeritasFeedRecommendationReason. -
The canonical editorial model (the contracts in
@oshun/contracts/veritasplus the editorial/source-quality/attestor/ counterclaim/topic-hub/retraction/contradiction modules). This is the editor-facing trust machinery the rest of this page documents.
The two are intentionally distinct. The presentational VeritasVerdict /
VeritasCredibilityTier are display vocabularies for a feed; the canonical
ClaimConfidenceBand / SourceQualityBand are the governance vocabularies a
reviewer attests against. Do not conflate them.
The shell read-adapter RBAC surface#
A third, frequently missed surface is the read-adapter role model in
adapter.ts. VERITAS_ADAPTER_READ_CAPABILITIES enumerates ten read
capabilities — contract_descriptor, metadata, availability, home_cards,
continue_items, search, launch, saved_articles, trending_topics,
bridge_contexts — and VERITAS_ADAPTER_ROLE_CAPABILITIES gates them per role:
| Role | Capabilities granted |
|---|---|
shell |
all ten (VERITAS_ADAPTER_READ_CAPABILITIES) |
admin |
contract_descriptor, metadata, availability, continue_items, search, saved_articles, trending_topics (no home_cards, launch, or bridge_contexts) |
assistant |
contract_descriptor, metadata, availability, continue_items, search, launch, bridge_contexts (no home_cards, saved_articles, or trending_topics) |
createVeritasReadAdapterRegistry returns { shell, admin, assistant } typed
views over the same canonical adapter, and
getVeritasAdapterReadCapabilities(role) returns the capability list for a role
(or the full ten when no role is given). The admin view deliberately drops
home_cards and launch (an operator does not need the reader home rail); the
assistant view drops the personalized saved_articles / trending_topics and
keeps launch and bridge_contexts so it can deep-link into the domain. This
is a real RBAC seam, not decoration.
Where Veritas lives in the product#
Consumer surfaces#
The consumer hub is /veritas, with presentational depth rendered by the
broadsheet-style VeritasRoom. The real apps/oshun/web/src/app/veritas/ route
tree contains nine subroutes:
claim— claim detail with confidence band and supporting/counter sourcescounterclaims— the side-by-side competing-view surfaceevidence— evidence-pack inspection (the inspect-evidence affordance)mobile— the mobile briefing / explainer surfaceprovenance— source-provenance and lineage inspectionretraction— the retraction-banner and correction-notice surfacesource— source inspection (kind, publisher, license, quality factors)story— the broadsheet story page with the evidence sidebartopic— the topic-hub reader surface
The original feature list at
V1/features.mdomitted/veritas/evidence, which exists as a real route directory and is the inspect-evidence surface distinct fromprovenance(source-level) andsource(single-source).
Power-user deep tools#
The apps/oshun/web/src/app/domains/veritas/ tree carries the journalism /
fact-checking deep tools that compose against the libs/veritas/* collection:
articles, bias, claims (plus claims/[claimId]), fact-check,
headlines, knowledge-graph, newsletter, nlp, research, story (plus
story/[id]), and topics (plus topics/[topicId]).
What V1 Veritas includes#
- Canonical models for stories, claims, sources, evidence, topics, timelines, counterclaims, unsupported claims, and trust/confidence display.
- Home and mobile briefing modules.
- Web story pages with evidence sidebars, claim detail pages, source inspection, counterclaims, topic hubs, and timelines.
- Mobile explainers and follow-up flows.
- Save/follow controls for stories, claims, and topics.
- Inspect-source and inspect-evidence interactions with Sophia-backed evidence bundles (see Sophia Grounding).
- Cross-domain links to Nisaba source lineage, Tara "metabolize this information" practices, Arete next actions, and assistant explainers that expose source inspection.
- Evaluation coverage for grounded explainer correctness, unsupported claims, citation integrity, trust/confidence display, story-to-source navigation, and web/mobile evidence inspection.
The canonical contract vocabulary#
The schemas in @oshun/contracts/veritas (libs/contracts/src/veritas/index.ts
plus attestor.ts) are the load-bearing vocabulary. They are Zod schemas with
superRefine invariants, so they are enforced at parse time, not just typed.
Source, claim, and evidence enums#
| Schema | Values |
|---|---|
SourceKindSchema |
15 kinds: peer-review, primary, secondary, press-release, opinion, social, government, ngo, wire, dataset, court-record, transcript, image, video, audio |
SourceQualityBandSchema |
high · mixed · low · contested |
ClaimConfidenceBandSchema |
well-supported · supported · emerging · contested · unsupported · retracted |
ClaimClassSchema |
9 classes: factual, statistical, quote, causal, prediction, interpretation, comparison, legal, scientific |
RetractionStateSchema |
active · under-review · retracted · superseded |
EvidenceStanceSchema |
supports · refutes · contextualizes · contested · neutral |
EvidenceLocatorGranularitySchema |
8 granularities: document, section, paragraph, sentence, table, figure, dataset-row, timestamp |
The earlier feature list (
V1/features.md) presented the sourcetypeenum as the 8 valuespeer-review/primary/secondary/press-release/opinion/social/government/NGO. The realSourceKindSchemais a superset of 15 — it also includeswire,dataset,court-record,transcript,image,video,audio. The extra kinds matter: the composite scorer infers peer-review status and primacy differently for each (acourt-recordis a primaryinstitutional-report; an unauthoredimageisunattributed).
The richer enums are not cosmetic. EvidenceStance includes contextualizes
(not every piece of evidence supports or refutes — some merely frames);
RetractionState distinguishes under-review (flagged but not yet pulled) from
superseded (replaced by a corrected version) from outright retracted; and
EvidenceLocatorGranularity goes all the way down to a timestamp (for an
audio/video citation) so a claim can cite the exact moment, not just "the
recording."
The three lifecycle vocabularies (and why they differ)#
This is a genuine source of confusion in the docs, so it is worth naming plainly: there are three distinct editorial-state vocabularies in play for what is loosely "the story lifecycle."
StoryEditorialStateSchema(contracts/src/veritas/index.ts:79-87) — the story record state stored on aStory:draft,review,published,updated,corrected,retracted,archived. It has no verification states at all — it is the coarse persisted state.VeritasEditorialState(editorial/state-machine.ts:1-13) — the runtime workflow state the editorial state machine drives, in kebab-case:draft,in-review,verifying-sources,awaiting-attestation,contradicts-existing,counterclaim-pending,approved,scheduled,published,archived,corrected,retracted.- The prose / diagrams in
V1/features.mdandV1/ARCHITECTURE.md, which historically used snake_case names (verifying_sources,awaiting_attestation, …) that match neither of the code vocabularies.
The implemented machine is the kebab-case VeritasEditorialState. There is no
snake_case state in the code. Where this page documents the state machine, the
kebab-case identifiers are authoritative; the snake_case in older diagrams is
illustrative spec, not the implemented graph.
Schema invariants worth knowing#
The superRefine blocks encode editorial policy directly in the contract. A
selection:
- Sources: a retracted source must carry both
retractedAtand areason; apeer-reviewsource cannot leavepeerReviewStatusasunknown; anarchiveUrlrequires a canonicalurl. - Claims: a
well-supportedclaim requires ≥ 2 supporting sources; anunsupportedclaim cannot carry supporting sources; acontestedclaim requires ≥ 1 counter-source; and aretractedretractionStateforces theconfidenceBandtoretracted. - Evidence packs: every evidence item's
claimId/sourceIdmust be declared in the pack'sclaimIds/sourceIds;reviewed/publishedpacks requirereviewedAt; incompletegroundingrequires amissingReason. - Correction notes:
priorPublicVersion.claimTextmust equalpriorText;visibleAtmust be after the prior version'spublishedAt; reader notifications must targetreaders-who-saw-prior-version. - Stories: a
publishedstory requires anevidencePackId; acorrectedstory requires correction notes; aretractedstory requires aretractionCascadeId.
These are real gates — a story cannot be persisted as published without an
evidence pack, full stop.
Source-quality scoring algorithm#
SourceQualityBand is the user-visible label
(high · mixed · low · contested); the underlying score is a deterministic
composite computed by computeSourceQualityComposite in
source-quality/composite.ts. Every source carries the full
SourceQualityScoreVector so editorial decisions, reviewer attestations, and
retraction cascades can reconstruct why a source landed where it did.
The nine factors#
The scorer takes nine factors. Eight are ratings; the ninth is a count, not a rating:
peerReviewStatus—peer-reviewed(1.0) /preprint(0.6) /institutional-report(0.65) /journalism(0.55) /press-release(0.35) /opinion(0.25) /social(0.15) /unattributed(0.1)primacy—primary(1.0) /secondary(0.65) /tertiary(0.4)editorialStandards—FactorRatingretractionHistory—FactorRatingexpertiseMatch—FactorRatingrecency—FactorRatingrightsClarity—FactorRatingindependence—FactorRating(disclosed conflicts / funder declarations)crossCorroboration— a count of independent peer sources, mapped throughcorroborationFactor:0 → 0.2,1 → 0.45,2 → 0.65,3 → 0.8,4 → 0.9,≥5 → 1.0
FactorRating is positive (1.0) / mixed (0.6) / unknown (0.4) /
negative (0.15). Every normalized factor is clamped to a floor of
FACTOR_FLOOR = 0.05 — no factor can be exactly zero, because the composite
is a geometric mean and a single zero would annihilate it.
Weighted geometric mean#
The composite is a weighted geometric mean, computed in log space:
contribution_i = weight_i · ln(normalized_i)
logMean = Σ contribution_i / Σ weight_i
geometric = exp(logMean) // in [0, 1]
composite = round(geometric · 1000) / 10 // mapped to [0, 100], one decimal
Geometric (not arithmetic) is the deliberate choice: it means one terrible
factor cannot be silently averaged away by several good ones. A source with
pristine recency, rights, and editorial standards but a negative (0.15)
retraction history is dragged down hard, exactly as an editor would want.
Per-domain weight tables#
Weights are published per claim domain in
source-quality/domain-weights.ts. The platform default is general@1.0.0;
specialist tables exist for medical, scientific, legal, statistical,
journalism, prediction, quote, causal, interpretation, comparison,
and factual. Each table is normalized to sum to 1.0 by buildWeights. The
differences are domain-true:
- Medical weights
peerReviewStatus(0.22) andcrossCorroboration(0.14) highest and de-emphasizesrecency(0.05) — randomized-trial evidence holds value across years. - Legal weights
primacy(0.28, court records and statutes) highest andpeerReviewStatus(0.04) almost to nothing — legal scholarship is rarely peer-reviewed in the medical sense. - Journalism weights
editorialStandards(0.22) andretractionHistory(0.14) highest. - Prediction weights
expertiseMatchandrecency(0.20 each) — forecasts decay quickly without recent calibration.
resolveDomainWeightTable resolves by explicit claimDomain, else by
claimClass, else falls back to general; rollbackDomainWeightTable refuses
to roll back past an initial release (no rollbackTargetVersion).
Banding and the hard overrides#
bandFromComposite applies deterministic thresholds, then three hard
overrides that can move a band regardless of the numeric composite:
| Composite | Base band |
|---|---|
≥ 82 |
high |
≥ 60 |
mixed |
≥ 35 |
low |
< 35 |
low |
(The thresholds are also exported as DEFAULT_BANDING_THRESHOLDS:
highMin: 82, mixedMin: 60, lowMin: 35.)
Then, in order:
unattributed-capped-low— ifpeerReviewStatus === 'unattributed'and the base band ishighormixed, it is forced down tolow.recent-retraction-drops-one-band— if the source publisher issued a retraction within the last 12 months (computed indifferenceInMonths/recentRetraction), the band drops one tier (high→mixed→low→contested).retracted-forces-contested— if the source itself is currently retracted, the band is forced tocontestedoutright.
Each applied override is recorded in hardOverridesApplied on the score vector,
and softer score-shaping notes (peer-review-status-unattributed,
no-cross-corroboration, reviewer-attestation-missing,
recent-retraction-within-12-months) are recorded in reasons. This is the
audit trail that lets a reviewer reconstruct the band.
Aspirational, not yet grounded here: the prose describes drift re-computation on a weekly cadence and per-tenant composite tightening (e.g., a research-lab tenant requiring a
mixedfloor). The composite is deterministic and re-runs on demand, and atenant-floor.tsmodule exists, but the scheduled weekly drift sweep is not wired in this code — treat it as planned.
Expert-attestation workflow#
Sources, claims, and stories with empirical weight require named expert
attestation; anonymous "trust us" attestation is forbidden. The contract is
ExpertAttestorSchema and AttestationSchema (attestor.ts), and the workflow
modules live in src/attestor/ (audit, conflict, credential-verification,
disagreement, expiry, probation, revocation, plus file-audit).
The attestor record#
ExpertAttestorSchema carries id, displayName, a credentialsBundle (array
of AttestorCredential), declaredExpertiseDomains (≥ 1, ≤ 20), conflicts[],
a nullable institutionalAffiliation, an attestationHistory[] (up to 2000
lifecycle entries), a suspensionState, a nullable probationUntil,
onboardedAt, lastReviewedAt, and an operatorReviewerId.
Invariants encoded directly in the schema:
- an attestor in
probationmust have aprobationUntildeadline (and only a probation attestor may set one); - an
activeattestor requires anoperatorReviewerIdof record — onboarding is operator-reviewed, not self-serve; credentialsBundleids must be unique.
AttestorSuspensionStateSchema is active · probation · suspended ·
revoked. AttestorCredentialKindSchema enumerates the proof kinds: degree,
professional-license, institutional-email, peer-attestation,
institutional-affiliation, publication-record, each with a status of
pending / verified / rejected / expired (a verified credential must
record verifiedBy; a rejected one must record a rejectionReason).
Attestation scope, cadence, conflicts#
AttestationScopeKindSchema is source · claim · counterclaim: an attestor
attests that a full source is reliable, that a specific claim from a source
is well-supported, or that a competing view is well-supported by an
alternative source. Each Attestation carries issuedAt / validUntil
(validity window — validUntil must be after issuedAt), a nullable
renewedFromId, a status (pending-review, admitted, soft-degraded,
withdrawn, revoked, contested), conflictsAtIssuance, a nullable
bandImpact, and a requiresReviewerBoard flag.
The conflict vocabulary (AttestorConflictKindSchema) is funder, employer,
family, financial, ideological-organization, undisclosed-financial,
professional-rivalry, other. A conflict incompatible with the claim domain
auto-flags the attestation: requiresReviewerBoard is set, and the schema
refuses to admit (status: 'admitted') any attestation still requiring
board sign-off. Expired attestations soft-degrade the source band one tier until
re-attested (the expiry module); revocation (revocation) re-bands and fires
a retraction-cascade pass; disagreement between two attestors (disagreement)
records both, surfaces the claim as a contested band, and feeds the editorial
state machine a contradiction event. Every lifecycle event
(issued/renewed/withdrawn/expired/overridden/rebanded/
flagged-for-review) appends to the attestationHistory audit trail.
Aspirational: the credential round-trip proof artifacts (e.g., an automated institutional-email round-trip or a public-record cross-check) are represented in the schema (
AttestorCredential.artifactUri, theinstitutional-emailcredential kind) but the live verification of those artifacts is an operator-review step, not an automated runtime in this code.
Counterclaim-balance rules#
A counterclaim is a competing view supported by its own sources, not a denial.
Veritas surfaces counterclaims when the evidence warrants — never gratuitously,
never to manufacture false balance. The logic is evaluateCounterclaimSurfacing
in counterclaim/surface.ts, and the decision is a four-way union:
type CounterclaimSurfacingDecision =
| { surface: 'co-equal'; reasons: string[] }
| { surface: 'minority-view-expand'; reasons: string[] }
| { surface: 'mandatory-surface'; reasons: string[] }
| { surface: 'suppress'; reasons: string[] }
The default policy#
DEFAULT_SURFACING_POLICY (surface.ts:50-62):
| Field | Value |
|---|---|
tenantBandFloor |
mixed |
consensusThreshold |
0.85 |
recencyWindowDays |
365 |
publicSafetyTopics |
public-health, product-safety, biosecurity, aviation-safety, nuclear-safety, natural-disaster-response |
How the decision is made#
The evaluator computes four surfacing conditions: the counterclaim's bandFloor
is at or above the tenant floor (isBandAtOrAbove); its strongest source is
independent of the primary's (no shared author, no shared funder, not the
same source id — isIndependent); the disagreement is substantive
(isSubstantive, classifier-assisted); and it is contemporary (within the
recency window of both the evaluation time and the primary publication —
withinRecencyWindow).
The decision order is deliberate:
- Mandatory-surface overrides everything.
mandatorySurfacingReasonsfires on a retraction-class disagreement, apluralismeditorial stance, or a public-safety topic (either the primary'seditorialCategoryispublic-safety, or atopicTagis inpublicSafetyTopics). When any of these holds, the counterclaim surfaces regardless of band or recency. - Suppress if band-floor, independence, or substantiveness fails — or if it is not contemporary.
- Minority-view-expand (the false-balance brake) —
shouldApplyFalseBalanceBrakefires when the primary band ishigh, the counter band islow, and theconsensusStrengthis at or above theconsensusThreshold(0.85). Instead of co-equal presentation, the reader gets a one-click "minority view exists" affordance. This is the rule that prevents Veritas from manufacturing balance on settled questions. - Co-equal only when all conditions pass and the brake does not engage.
Every decision returns the rule(s) applied in reasons (e.g.
false-balance-brake-engaged, mandatory-public-safety-topic,
counter-below-band-floor), so the surfacing decision is fully auditable
(counterclaim/audit.ts, file-audit.ts). Per-reader preferences ("always show
counterclaims" / opt out of minority-view expands) live in
counterclaim/reader-preferences.ts.
Topic-hub composition algorithm#
A topic hub organizes stories, claims, sources, timelines, and counterclaims
around a topic anchor. composeTopicHub in topic-hub/composer.ts builds a hub
from candidate sets across six sections, in this order
(TOPIC_HUB_SECTIONS):
latest · key-facts · under-dispute · corrections · background ·
timeline
Ranking#
DEFAULT_RANKING_WEIGHTS (composer.ts:94-100):
| Weight | Value |
|---|---|
freshness |
0.30 |
proximity |
0.25 |
bandQuality |
0.20 |
engagement |
0.15 |
attestation |
0.10 |
Each section ranks its own candidate kind with a section-appropriate score:
latestrankspublished/updatedstories by freshness × proximity × engagement × attestation. Freshness uses an exponential decayexp(-ageDays / 14)— a two-week half-life-ish curve.key-factsfilters to claims at thehighsource-quality floor with awell-supported/supportedconfidence band, scored on band quality, attestation, proximity, and half-weighted freshness.under-disputefilters tocontested/emergingclaims.correctionsranks by severity weight (notification-included3,banner-only2,silent1) plus recency.backgroundranks linkednisaba-explainer/metis-lesson/veritas-explaineritems by proximity plus half-weighted freshness.timelineranks events by significance weight (high3,medium2,low1) plus recency.
Ranking is deterministic: ties break by id.localeCompare, so the same
candidate set always produces the same ordering — exactly what the determinism
test asserts. Editorial gating (topic-hub/editorial-gating.ts) ensures nothing
surfaces automatically — every proposed candidate passes through the editorial
state machine first, and operators can pin, demote, or block with rationale.
Refresh is event-triggered (topic-hub/refresh.ts).
Aspirational: localized hub variants (per-locale
latest/backgroundordering) are represented bytopic-hub/locale-variant.tsand thelocalefield threaded through composition, but full localized parity is planned, not shipped.
Story update, correction, and retraction#
StoryUpdateKindSchema enumerates the update events: new-evidence,
retracted-source, contested-claim, new-counterclaim, corrected-claim —
each captured with timestamp, rationale, and reviewerId, and with
kind-specific invariants (a corrected-claim requires a correctionNoteId; a
retracted-source requires a retractionCascadeId; etc.).
When a prior public claim changes, a CorrectionNote surfaces the change to the
right audience: CorrectionReaderAudienceSchema is
readers-who-saw-prior-version or all-current-readers, and the schema
enforces that reader notifications target only those who saw the prior
version, with a seenPriorVersionCutoffAt cutoff. Story-update notifications
are per-user, per-tenant, per-followed-topic (story-update-notifications.ts,
story-update-notification-dispatch.ts).
The retraction-cascade contract#
When a source retracts, every dependent surface re-grounds. The plan is the
RetractionCascade contract (RetractionCascadeSchema,
contracts/src/veritas/index.ts:549-767). It tracks, separately:
affectedStoryIds,affectedClaimIds,affectedExplainerIds,affectedEvidencePackIds— the in-Veritas fan-out;downstreamMetisPackageIdsanddownstreamMetisLessonIds— the cross-domain fan-out into Metis.
Older prose said only "downstream Metis lessons re-validated." The schema tracks two Metis fan-outs: package ids and lesson ids. The
reGroundingJobscarry anactionenum (RetractionCascadeJobActionSchema = 're-ground' | 'metis-revalidate'), and asuperRefineforces everymetis-lessonjob tometis-revalidateand every non-Metis job tore-ground. The schema also requires a re-grounding job for every affected target (a missing job target is a validation error), requirescompletedAton completed jobs andfailureReasonon failed jobs, and requires that acompletedcascade have every job completed and (if a user notice was required) the notice surfaced.
The execution / event seam#
The planner is pure; the executor makes the cascade durable and dispatches
it. executeVeritasSourceRetractionCascade (retraction-cascade-execution.ts)
does plan → persist → publish, in that deliberate order: the cascade is
persisted before the dispatch event is published, so a crash between the two
never emits a dispatch for a non-durable cascade. The pairing is at-least-once —
a recovery sweep can safely re-publish a persisted-but-unpublished cascade
because consumers key off the deterministic cascadeId / per-job jobId.
The dispatch event constant is
VERITAS_RETRACTION_CASCADE_DISPATCHED_EVENT = 'veritas.retraction.cascade.dispatched'
(retraction-cascade-execution.ts:37). The library stays infra-free: it depends
only on small injected ports (RetractionCascadePersistencePort,
RetractionCascadeEventPort), and the app boundary wires the real
ContractPersistenceService and IEventBus.
The cascade worker#
@oshun/veritas-cascade-worker is the event-bus composition root.
subscribeRetractionCascadeWorker subscribes an IEventBus (from
@oshun/event-bus) to the dispatch topic and routes each event's per-target
jobs through consumeRetractionCascadeDispatch. The downstream re-grounders are
real and persistence-backed:
createPersistenceBackedSophiaReGrounderloads a grounded answer and its current (post-retraction) source texts from theDurableSnapshotStore(@oshun/persistence), re-scores groundedness with the realcreateSophiaReGrounderfrom@sophia/evaluation, and records the verdict back. Store keys:SOPHIA_GROUNDED_ANSWER_STORE_KEY = 'sophia-grounded-answer',SOPHIA_REGROUNDING_VERDICT_STORE_KEY = 'sophia-regrounding-verdict'.createPersistenceBackedMetisRevalidatordoes the same shape for Metis lessons viacreateMetisLessonRevalidatorfrom@metis/research. Store keys:METIS_LESSON_SOURCES_STORE_KEY = 'metis-lesson-sources',METIS_REVALIDATION_VERDICT_STORE_KEY = 'metis-revalidation-verdict'.
Honest end-to-end status: the ports and persistence-backed re-grounders exist and are unit/integration-tested, but the triage/audit notes record the customer-facing retraction-cascade journey (full fan-out, notebook and living-offering surfaces, per-user gating) as only partial end-to-end in e2e. The "downstream Metis lessons re-validated" runtime is wired at the deployable boundary, not exercised end-to-end here.
Retraction-cascade UX#
What the customer experiences is built in retraction-ux/banner.ts. There are
four surface kinds (RetractionSurfaceKind): story-page, saved-claim,
notebook, living-offering. Severity is computed by severityFromBandChange
into three RetractionSeverity levels:
| Severity | When |
|---|---|
notification-included |
source retracted, or a band drop of ≥ 2 tiers |
banner-only |
a band drop of exactly 1 tier |
silent |
no band drop (copy edit) |
Per-surface builders attach the right affordances: buildStoryPageBanner and
buildSavedClaimBanner offer remove-from-library, view-corrected-source,
and appeal-or-correct; buildNotebookNotice offers open-citation-drill-down
instead of remove-from-library; buildLivingOfferingBanner offers a
re-render-offer. The kept-Veritas-Living-Offering case (a pinned Sophia source
later corrected) surfaces a banner and offers re-render under the corrected
source — see
Living Scenes — Concept and Customer Promise.
Aspirational: reader-notification quiet-hours integration is described in the prose but is not wired into the banner builders here — the severity gate is real, the quiet-hours routing is planned.
retraction-ux/recourse.tsandtenant-variant.tscarry the appeal-routing and tenant-variant seams.
Contradiction-detection loop with Sophia#
Sophia surfaces semantic contradictions; Veritas treats them as editorial
events. The probe is runContradictionProbe in contradiction/probe.ts.
The trigger vocabulary (ContradictionTrigger) is new-claim-published,
source-update, attestor-disagreement, operator-flag, cadence-sweep,
on-refresh. The probe calls an injected SophiaProbeClient, which returns a
SophiaEvidencePack (from @oshun/evidence-sophia); each evidence item is
mapped to a ContradictionPair with a stance (supporting / contradicting /
neutral), a confidence, and a relevance. Pairs are sorted by confidence then
relevance, and triaged to the reviewer queue when
stance === 'contradicting' and confidence >= triageThreshold.
DEFAULT_PROBE_OPTIONS.triageThreshold is 0.6. Reviewer routing
(contradiction/reviewer-routing.ts), continuous monitoring (monitoring.ts),
and per-tenant time-boxing (tenant-timebox.ts) build on the probe.
Contradictions surface to the queue rather than silently mutating any reader
surface — that is the design invariant.
The Veritas editorial state machine#
The generic editorial lifecycle (draft → in-review → approved → scheduled →
published → archived/retracted) is extended for the news domain by the
implemented VeritasEditorialState machine in editorial/state-machine.ts.
attemptTransition validates a transition against TRANSITION_RULES,
InMemoryTransitionLog records each TransitionRecord, and replayHistory
re-derives the current state from a record stream (throwing if the chain is
broken).
The real transition graph#
The implemented states are the kebab-case VeritasEditorialState union
(draft, in-review, verifying-sources, awaiting-attestation,
contradicts-existing, counterclaim-pending, approved, scheduled,
published, archived, corrected, retracted). The implemented edges
(TRANSITION_RULES, lines 58-141) are:
| From | Event | To | Gate(s) |
|---|---|---|---|
draft |
submit-for-review |
in-review |
rationale |
in-review |
sources-ingested |
verifying-sources |
sources |
verifying-sources |
sources-verified |
awaiting-attestation |
sources + sources-verified |
verifying-sources |
contradiction-flagged |
contradicts-existing |
contradictions |
awaiting-attestation |
attestation-collected |
approved |
attestations |
awaiting-attestation |
contradiction-flagged |
contradicts-existing |
contradictions |
awaiting-attestation |
counterclaim-required |
counterclaim-pending |
rationale |
contradicts-existing |
contradiction-resolved |
awaiting-attestation |
contradictions resolved |
counterclaim-pending |
counterclaim-resolved |
approved |
counterclaims |
approved |
counterclaim-required |
counterclaim-pending |
rationale |
approved |
schedule |
scheduled |
rationale |
approved |
publish |
published |
rationale |
scheduled |
publish |
published |
rationale |
published |
archive |
archived |
rationale |
published |
issue-correction |
corrected |
rationale + cascade-scope |
published |
retract |
retracted |
rationale + cascade-scope |
corrected |
retract |
retracted |
rationale + cascade-scope |
Two corrections to older docs. (1) There is no direct
awaiting-attestation → publishededge; the path goesawaiting-attestation --attestation-collected--> approved, thenapproved --publish--> published(or viascheduled). The "golden path" in older prose skippedapproved. (2) TheARCHITECTURE.mdmermaid that routes verification branches intoin_reviewis illustrative spec — the implemented machine routesverifying-sourcestoawaiting-attestationandawaiting-attestationtoapproved. The diagram and the code diverge in both node set and edges; the code is authoritative.
The gates#
Every transition runs the three universal gates — requireReviewer,
requireRationale, requireOccurredAt — plus its rule-specific gates:
requireSources, requireSourcesVerified, requireAttestations (also blocks
until openAttestationCount === 0), requireContradictions,
requireOpenContradictionsResolved, requireCounterclaims (blocks until
openCounterclaimCount === 0), and requireCascadeScope (a correction or
retraction must declare its cascade scope). A failed gate returns
{ ok: false, gateFailures: [...] } with the specific reasons (e.g.
attestation-references-required, contradictions-still-open,
cascade-scope-required) and does not append a record. This is why
awaiting-attestation, contradicts-existing, and counterclaim-pending are
genuine blocking states: there is no edge out of them to publication that does
not first satisfy their gate.
Tests and what they guarantee#
The test suites verify domain correctness, not just data flow:
- Source-quality composite determinism (
composite.test.ts): same factor vector → same composite → same band; per-domain weight tables honored; the three hard overrides fire on their exact conditions. - Expert attestation (
attestor.test.ts): credential-verification gating, conflict-flag auto-route to the reviewer board, disagreement surfaces as a contested band, revocation fires re-banding. - Counterclaim surfacing (
counterclaim-balance.test.ts): golden-path substantive disagreement surfaces co-equal; the false-balance brake suppresses to minority-view-expand; mandatory-surface overrides fire; reader-preference toggles honored. - Topic-hub composition (
topic-hub.test.ts): candidate ranking determinism, editorial gating enforced, locale variant parity. - Retraction-cascade UX (
retraction-ux.test.ts): every dependent surface receives the right banner severity; appeals route correctly. - Contradiction loop (
contradiction.test.ts,execution.test.ts): contradictions at/above 0.6 reach the queue; per-tenant time-box enforced. - Editorial state machine (
state-machinetests): every gate enforced; blocking states block publication until resolved;replayHistoryreconstructs state. - Cascade execution and worker (
retraction-cascade-execution.test.ts,veritas-cascade-worker.integration.test.ts,persistence-regrounders.integration.test.ts): plan → persist → publish ordering, event subscription, and persistence-backed re-grounding over a real durable store.
Related#
- Sophia Grounding — the evidence/contradiction backend the contradiction probe and re-grounders depend on
- Metis — Education and Tutoring — the downstream domain whose packages and lessons the retraction cascade re-validates
- Nisaba — Scholarly Study — source-lineage and study prompts Veritas cross-links to
- Tara — Rituals and Contemplative Practice — the "metabolize this information" practices Veritas bridges into
- Arete — Goals, Habits, and Reflection — the next-actions Veritas surfaces
- Living Scenes — Concept and Customer Promise and Keep, Share, Shareability, Takedown, and Lineage — the Grounded Explainer Living Offering and re-render-under-corrected-source flow
- Search, Discovery, Recommendations, and Knowledge Graph
— the
libs/veritas/*journalism and knowledge-graph deep tools - Review, Compliance, and Trust & Safety — the editorial review queue and audit surfaces
- Customer Domains — the companion runtime view of Veritas's contracts and surfaces
- Subsystem Glossary — shared vocabulary
../features.md— feature hub