1/* Shared browser/CLI research audit. Integrity is not authorship or historical truth. */ 2(function (g) { 3'use strict'; 4const I = g.SymbolonInvestigation; 5const hash = value => I.hash(JSON.stringify(value)); 6const sourceText = record => typeof record.raw.fulltext === 'string' ? record.raw.fulltext : typeof record.raw.text === 'string' ? record.raw.text : ''; 7const ref = id => ({'@id': 'urn:symbolon:' + encodeURIComponent(id)}); 8function requireThat(ok, reason) { if (!ok) throw new Error(reason); } 9 10async function audit(state, reviews = state.reviews || []) { 11 state = await I.verify(state); 12 requireThat(Array.isArray(reviews), 'Reviews must be an array'); 13 const findings = [], add = (code, id, detail) => findings.push({code, id, detail}); 14 const ids = [...state.revisions, ...state.sourceSets, ...state.artifacts, ...state.receipts, ...(state.hypotheses || []), ...(state.plans || [])].map(x => x.id); 15 requireThat(new Set(ids).size === ids.length, 'IDs collide across investigation entity types'); 16 const exists = (list, id) => id == null || list.some(x => x.id === id); 17 state.revisions.forEach((r, n) => { 18 requireThat(r.parent === (n ? state.revisions[n - 1].id : null), 'Broken ordered revision chain'); 19 requireThat(exists(state.sourceSets, r.sourceSet) && exists(state.artifacts, r.artifact), 'Broken revision reference'); 20 }); 21 function parents(list) { 22 const visited = new Set(); 23 list.forEach(x => { requireThat(x.parent == null || visited.has(x.parent), 'Parent must precede child: ' + x.id); visited.add(x.id); }); 24 } 25 parents(state.sourceSets); parents(state.artifacts); 26 const evidenceIDs = new Map(); 27 for (const set of state.sourceSets) for (const record of set.records) { 28 requireThat(typeof record.id === 'string' && !ids.includes(record.id), 'Invalid evidence ID'); 29 const fingerprint = await hash(record); 30 requireThat(!evidenceIDs.has(record.id) || evidenceIDs.get(record.id) === fingerprint, 'Conflicting evidence ID'); 31 evidenceIDs.set(record.id, fingerprint); 32 } 33 const knownReviewArtifacts = new Set(); 34 for (const review of reviews) { 35 const a = state.artifacts.find(x => x.id === review.artifactId); 36 requireThat(a && review.artifactSha256 === await hash(a), 'Review is stale or refers to an unknown artifact'); 37 requireThat(typeof review.reviewer === 'string' && review.reviewer.trim() && typeof review.reviewedAt === 'string' && Number.isFinite(Date.parse(review.reviewedAt)), 'Reviewer and review date required'); 38 requireThat(!knownReviewArtifacts.has(a.id), 'Duplicate artifact review'); knownReviewArtifacts.add(a.id); 39 requireThat(review.scope === 'all-source-claims' && Array.isArray(review.claims), 'Review must declare its claim inventory scope'); 40 requireThat(typeof review.coverageNote === 'string' && review.coverageNote.trim(), 'Reviewer must explain claim coverage'); 41 const records = state.sourceSets.find(s => s.id === a.sourceSet)?.records || []; 42 const seen = new Set(); 43 for (const c of review.claims) { 44 requireThat(typeof c.id === 'string' && c.id.trim() && !seen.has(c.id), 'Duplicate or missing claim ID'); seen.add(c.id); 45 requireThat(typeof c.text === 'string' && c.text.trim() && a.content.includes(c.text), 'Claim text must occur in the reviewed artifact'); 46 requireThat(['supported', 'contradicted', 'unresolved', 'interpretation'].includes(c.status), 'Invalid claim review status'); 47 requireThat(typeof c.reason === 'string' && c.reason.trim() && Array.isArray(c.evidence), 'Review reason and evidence array required'); 48 for (const e of c.evidence) { 49 const source = records.find(r => r.id === e.recordId); 50 requireThat(source, 'Review cites evidence outside the artifact source set'); 51 requireThat(typeof e.quote === 'string' && e.quote.trim() && sourceText(source).includes(e.quote), 'Evidence quote is absent from the saved source text'); 52 } 53 if (c.status === 'supported') requireThat(c.evidence.length > 0, 'Supported claim needs saved quoted evidence'); 54 if (c.status !== 'supported') add('claim-' + c.status, c.id, c.reason); 55 } 56 if (!review.claims.length) add('empty-claim-review', a.id, 'No source claims inventoried; this cannot establish support coverage'); 57 } 58 for (const set of state.sourceSets) for (const r of set.records) { 59 if (r.raw.identity_status || r.anchor.identityStatus) add('source-identity-unresolved', r.id, r.raw.identity_status || r.anchor.identityStatus); 60 if (!r.anchor.sourceVersion) add('source-version-unknown', r.id, 'Saved record hash is not an external edition/version identifier'); 61 if (!r.anchor.rightsStatus || r.anchor.rightsStatus === 'unknown') add('rights-unresolved', r.id, 'Private package does not grant redistribution permission'); 62 if (r.anchor.requestedLocus && r.anchor.requestedLocus !== r.anchor.resolvedLocus) add('locus-unresolved', r.id, 'Requested and resolved locus differ'); 63 if (!r.anchor.resolvedLocus) add('locus-unrecorded', r.id, 'Exact source location is not recorded'); 64 } 65 for (const a of state.artifacts) { 66 const rows = I.rows(state, a.sourceSet), gate = globalThis.SymbolonEvidence.gate(a.content, rows); 67 if (!gate.grounded) add('citation-resolution-incomplete', a.id, 'Missing, ambiguous or unresolved citations; not a claim-support test'); 68 if (!knownReviewArtifacts.has(a.id)) add('support-unreviewed', a.id, 'No hash-bound human claim review'); 69 if (a.status !== 'complete') add('artifact-incomplete', a.id, String(a.status)); 70 } 71 for (const r of state.receipts) { 72 if (!['tool', 'model'].includes(r.kind)) continue; 73 if (r.result?.status !== 'complete') add('operation-incomplete', r.id, r.result?.status || 'unknown'); 74 const raw = r.result?.raw || {}, provenance = raw.provenance || r.result?.provenance; 75 if (raw.pilot || r.result?.pilot || provenance?.kind === 'pilot') add('pilot-output', r.id, 'Pilot output cannot qualify the upstream specialist'); 76 if (r.kind === 'model' && !r.identity) add('model-identity-missing', r.id, 'No conversational model identity recorded'); 77 if (['restore_text', 'attribute_text'].includes(r.name) && !provenance) add('specialist-provenance-missing', r.id, 'No specialist identity recorded'); 78 } 79 if (!state.artifacts.length) add('no-artifact', state.id, 'There is no research artifact to review'); 80 return {integrity: 'PASS', reviewReadiness: findings.length ? 'INCOMPLETE' : 'RECORDED_CHECKS_COMPLETE', findings, 81 limits: ['Checksums detect changed bytes; this unsigned package does not authenticate authorship.', 'Human claim coverage and support are self-declared, not automatically certified.', 'No model accuracy, historical truth, licence clearance or publication readiness is certified.']}; 82} 83async function provenance(state, reviews) { 84 const graph = []; 85 for (const receipt of state.receipts) graph.push({...ref(receipt.id), '@type': 'prov:Activity', 'sym:receiptHash': await hash(receipt), 'sym:kind': receipt.kind}); 86 for (const set of state.sourceSets) for (const r of set.records) graph.push({...ref(r.id), '@type': 'prov:Entity', 'sym:citationKey': r.citationKey, 'sym:contentHash': r.anchor.sourceContentHash, 87 ...(r.receiptId ? {'prov:wasGeneratedBy': ref(r.receiptId)} : {})}); 88 for (const a of state.artifacts) graph.push({...ref(a.id), '@type': 'prov:Entity', 'sym:contentHash': await hash(a), 89 'prov:wasDerivedFrom': (state.sourceSets.find(s => s.id === a.sourceSet)?.records || []).map(r => ref(r.id))}); 90 for (const [n, review] of reviews.entries()) { 91 const reviewer = 'reviewer-' + await hash(review.reviewer), activity = state.id + '-review-' + n; 92 graph.push({...ref(reviewer), '@type': 'prov:Agent', 'sym:selfDeclaredName': review.reviewer}); 93 graph.push({...ref(activity), '@type': 'prov:Activity', 'prov:used': ref(review.artifactId), 'prov:wasAssociatedWith': ref(reviewer), 'sym:reviewHash': await hash(review)}); 94 } 95 return {'@context': {prov: 'http://www.w3.org/ns/prov#', sym: 'https://magalia.wiki/ns/symbolon#'}, '@graph': graph}; 96} 97async function bundle(state, reviews = state.reviews || []) { 98 state = await I.verify(state); reviews = I.copy(reviews); 99 if (state.reviews?.length) requireThat(await hash(state.reviews) === await hash(reviews), 'Embedded and supplied reviews differ'); 100 const payload = {investigation: state, reviews, audit: await audit(state, reviews), provenance: await provenance(state, reviews)}; 101 return {format: 'symbolon-private-research-package-v1', privateOnly: true, payload, 102 sha256: Object.fromEntries(await Promise.all(Object.entries(payload).map(async ([k, v]) => [k, await hash(v)])))}; 103} 104async function verifyPackage(pkg) { 105 requireThat(pkg.format === 'symbolon-private-research-package-v1' && pkg.privateOnly === true, 'Unsupported private package'); 106 const keys = ['audit', 'investigation', 'provenance', 'reviews']; 107 requireThat(pkg.payload && pkg.sha256 && JSON.stringify(Object.keys(pkg.payload).sort()) === JSON.stringify(keys) && JSON.stringify(Object.keys(pkg.sha256).sort()) === JSON.stringify(keys), 'Incomplete package manifest'); 108 for (const k of keys) requireThat(await hash(pkg.payload[k]) === pkg.sha256[k], 'Package checksum mismatch: ' + k); 109 const current = await bundle(pkg.payload.investigation, pkg.payload.reviews); 110 requireThat(await hash(current.payload.audit) === pkg.sha256.audit && await hash(current.payload.provenance) === pkg.sha256.provenance, 'Audit/provenance does not correspond to the saved investigation'); 111 return current.payload.audit; 112} 113g.SymbolonResearch = {audit, bundle, verifyPackage, hash, sourceText}; 114})(typeof window !== 'undefined' ? window : globalThis);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.