Automerge backed Markdown editor for the Atmosphere
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211#!/usr/bin/env node/** * Build-independent orchestration for the version-pinned Obsidian Bases oracle. * * Commands: * prepare [--out PATH] stage a disposable Obsidian fixture vault * check validate source cases and an optional corpus * validate [RAW] [--run PATH] fail closed on malformed/incoherent output * promote [RAW] [--run PATH] write a review candidate under node_modules * promote ... --accept replace the committed golden after validation * * Exit codes: 0 success, 1 semantic/validation failure, 2 usage failure. */import crypto from 'node:crypto'import { spawnSync } from 'node:child_process'import fs from 'node:fs'import os from 'node:os'import path from 'node:path'import { fileURLToPath } from 'node:url'import YAML from 'yaml'
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')const HARNESS_ROOT = path.join(ROOT, 'tests', 'bases-oracle')const CASES_PATH = process.env.BASES_ORACLE_CASES ?? path.join(HARNESS_ROOT, 'cases.json')const CASE_INPUT_ROOT = path.join(HARNESS_ROOT, 'cases')const RELATIONS_PATH = process.env.BASES_ORACLE_RELATIONS ?? path.join(HARNESS_ROOT, 'relations.json')const INVENTORY_PATH = process.env.BASES_ORACLE_INVENTORY ?? path.join(HARNESS_ROOT, 'coverage-inventory.json')const OFFICIAL_SYNTAX_INVENTORY_PATH = process.env.BASES_ORACLE_OFFICIAL_INVENTORY ?? path.join(HARNESS_ROOT, 'official-syntax-inventory.json')const FIXTURE_ROOT = path.join(HARNESS_ROOT, 'fixture-vault')const PLUGIN_ROOT = path.join(HARNESS_ROOT, 'plugin')const CORPUS_POLICY_PATH = process.env.BASES_ORACLE_CORPUS_POLICY ?? path.join(HARNESS_ROOT, 'corpus-policy.json')const API_SNAPSHOT_PATH = path.join(ROOT, 'vendor', 'obsidian-1.11.4.d.ts')const DEFAULT_STAGE = path.join(ROOT, 'node_modules', '.tmp', 'bases-oracle', 'vault')const DEFAULT_PLUGIN_BUILD = path.join(ROOT, 'node_modules', '.tmp', 'bases-oracle', 'plugin', 'main.js')const DEFAULT_RAW = path.join(DEFAULT_STAGE, '_bases_oracle', 'raw-results.json')const DEFAULT_RUN = path.join(DEFAULT_STAGE, '_bases_oracle', 'run.json')const CANDIDATE_PATH = process.env.BASES_ORACLE_CANDIDATE ?? path.join(ROOT, 'node_modules', '.tmp', 'bases-oracle', 'candidate-v1.json')const CORPUS_PATH = process.env.BASES_ORACLE_CORPUS ?? path.join(HARNESS_ROOT, 'corpus', 'v1.json')const VIEW_TYPE = 'reams-bases-oracle'const EXPECTED_OBSIDIAN_VERSION = '1.11.4'const EXPECTED_API_COMMIT = '4c5889aa06d3ceb3d4d3a20931fbeaede6d399a0'const OFFICIAL_SYNTAX_INVENTORY_SHA256 = '123701fd3bd844f35374254f8aa77512288b5017d0c920f43d75995429457b78'const SCRIPT_PATH = fileURLToPath(import.meta.url)const VITE_BIN = process.env.BASES_ORACLE_VITE_BIN ?? [ path.join(ROOT, 'node_modules', 'vite', 'bin', 'vite.js'), path.resolve(ROOT, '..', '..', 'node_modules', 'vite', 'bin', 'vite.js'),].find(candidate => fs.existsSync(candidate)) ?? path.join(ROOT, 'node_modules', 'vite', 'bin', 'vite.js')const VITE_CONFIG = path.join(PLUGIN_ROOT, 'vite.config.ts')
class OracleValidationError extends Error {}
const EMPTY_ROW_CATEGORIES = Object.freeze({ 'zero-limit': 'A zero limit intentionally produces no result rows.',})
function fail(message, usage = false) { console.error(message) process.exitCode = usage ? 2 : 1}
function object(value, label) { if (!value || typeof value !== 'object' || Array.isArray(value)) { throw new OracleValidationError(`${label} must be an object`) } return value}
function nonEmptyString(value, label) { if (typeof value !== 'string' || value.length === 0) { throw new OracleValidationError(`${label} must be a non-empty string`) } return value}
function stringArray(value, label) { if (!Array.isArray(value) || !value.every(item => typeof item === 'string')) { throw new OracleValidationError(`${label} must be an array of strings`) } return value}
function exactKeys(value, keys, label) { const actual = Object.keys(object(value, label)).sort() const allowed = new Set(keys) const unexpected = actual.filter(key => !allowed.has(key)) if (unexpected.length > 0) { throw new OracleValidationError(`${label} has unexpected keys: ${unexpected.join(', ')}`) } return value}
function optionalMetadata(value, label, keys) { if (value === null || value === undefined) return exactKeys(value, keys, label) for (const key of keys) { if (key === 'seed' && value[key] !== undefined && !Number.isInteger(value[key])) { throw new OracleValidationError(`${label}.seed must be an integer`) } if (key !== 'seed' && value[key] !== undefined) nonEmptyString(value[key], `${label}.${key}`) }}
function validateEmptyRowAllowance(value, label) { if (value === undefined) return const allowance = object(value, label) exactKeys(allowance, ['category', 'rationale'], label) nonEmptyString(allowance.category, `${label}.category`) nonEmptyString(allowance.rationale, `${label}.rationale`) if (!(allowance.category in EMPTY_ROW_CATEGORIES)) throw new OracleValidationError(`${label}.category is not registered`) if (allowance.rationale !== EMPTY_ROW_CATEGORIES[allowance.category]) throw new OracleValidationError(`${label}.rationale does not match the registered category`)}
function validateEmptyCaseProbes(testCase, label) { if (testCase.capture.allowEmptyRows === undefined) return if (testCase.probes.properties.length !== 0 || (testCase.probes.summaries ?? []).length !== 0) { throw new OracleValidationError(`${label} expected-empty cases may not declare property or summary probes`) }}
function probe(value, label) { const candidate = object(value, label) exactKeys(candidate, ['property', 'outcome'], label) nonEmptyString(candidate.property, `${label}.property`) if (!['success', 'error', 'null'].includes(candidate.outcome)) throw new OracleValidationError(`${label}.outcome must be success, error, or null`) return candidate}
function readJson(file, label = file) { let parsed try { parsed = JSON.parse(fs.readFileSync(file, 'utf8')) } catch (error) { throw new OracleValidationError(`Unable to parse ${label}: ${error instanceof Error ? error.message : String(error)}`) } return parsed}
function validateFilter(value, label) { if (typeof value === 'string') return const candidate = object(value, label) const keys = ['and', 'or', 'not'].filter(key => key in candidate) if (keys.length !== 1) throw new OracleValidationError(`${label} must contain exactly one of and/or/not`) const children = candidate[keys[0]] if (!Array.isArray(children)) throw new OracleValidationError(`${label}.${keys[0]} must be an array`) children.forEach((child, index) => validateFilter(child, `${label}.${keys[0]}[${index}]`))}
function validateStringRecord(value, label) { const candidate = object(value, label) for (const [key, item] of Object.entries(candidate)) { nonEmptyString(key, `${label} key`) if (typeof item !== 'string') throw new OracleValidationError(`${label}.${key} must be a string`) }}
function caseInputPath(testCase, index) { const relative = nonEmptyString(testCase.inputPath, `cases[${index}].inputPath`) if (relative.includes('\\') || path.isAbsolute(relative) || relative.split('/').includes('..') || !relative.endsWith('.base')) { throw new OracleValidationError(`case '${testCase.id ?? index}' inputPath is unsafe or not a .base file`) } const absolute = path.join(CASE_INPUT_ROOT, ...relative.split('/')) if (!fs.existsSync(absolute) || !fs.statSync(absolute).isFile()) { throw new OracleValidationError(`case '${testCase.id ?? index}' inputPath does not exist: ${relative}`) } return { relative, absolute }}
function validateRawBase(bytes, label, expectedViewName, coverageTags = []) { let base try { base = YAML.parse(bytes.toString('utf8')) } catch (error) { throw new OracleValidationError(`${label} is not valid YAML: ${error instanceof Error ? error.message : String(error)}`) } const root = object(base, label) const reserved = new Set(['oracleCaseId', 'oracleProperties', 'oracleSummaries', 'oracleExpectedFiles', 'oraclePreserveRowOrder']) const visit = (value) => { if (!value || typeof value !== 'object') return if (Array.isArray(value)) { value.forEach(visit) return } for (const [key, child] of Object.entries(value)) { if (reserved.has(key)) throw new OracleValidationError(`${label} contains reserved oracle key '${key}'`) visit(child) } } visit(root) if (root.formulas !== undefined) validateStringRecord(root.formulas, `${label}.formulas`) const folderRestriction = 'file.inFolder("_bases_oracle/rows")' const hasFolderRestriction = (value) => { if (value === folderRestriction) return true if (!value || typeof value !== 'object' || Array.isArray(value)) return false if (!Array.isArray(value.and)) return false return value.and.some(hasFolderRestriction) } if (!hasFolderRestriction(root.filters)) { throw new OracleValidationError(`${label}.filters must retain ${folderRestriction}`) } if (root.filters && typeof root.filters === 'object' && !Array.isArray(root.filters) && !Array.isArray(root.filters.and)) { throw new OracleValidationError(`${label}.filters must compose additional restrictions with top-level and`) } if (!Array.isArray(root.views) || root.views.length === 0) throw new OracleValidationError(`${label}.views must be a non-empty array`) const oracleViews = root.views.filter(view => view && typeof view === 'object' && view.type === VIEW_TYPE) if (oracleViews.length !== 1) throw new OracleValidationError(`${label} must contain exactly one '${VIEW_TYPE}' view`) if (expectedViewName !== undefined && oracleViews[0].name !== expectedViewName) { throw new OracleValidationError(`${label} oracle view name must be '${expectedViewName}'`) } if (coverageTags.some(tag => tag.startsWith('pipeline.'))) { const sort = oracleViews[0].sort if (!Array.isArray(sort) || sort.length === 0) { throw new OracleValidationError(`${label} pipeline view must declare an explicit sort`) } const lastSort = sort[sort.length - 1] if (!lastSort || typeof lastSort !== 'object' || lastSort.property !== 'file.path') { throw new OracleValidationError(`${label} pipeline sort must end with a file.path tie-breaker`) } } for (const [index, rawView] of root.views.entries()) { const view = object(rawView, `${label}.views[${index}]`) if (view.type !== VIEW_TYPE) continue } return root}
function validateCases(value) { const root = object(value, 'cases manifest') exactKeys(root, ['schemaVersion', 'normalizationVersion', 'target', 'locale', 'timezone', 'seed', 'cases'], 'cases manifest') if (root.schemaVersion !== 1 || root.normalizationVersion !== 1) { throw new OracleValidationError('cases manifest versions must both equal 1') } const target = object(root.target, 'cases manifest.target') exactKeys(target, ['obsidianVersion', 'apiCommit'], 'cases manifest.target') if (target.obsidianVersion !== EXPECTED_OBSIDIAN_VERSION || target.apiCommit !== EXPECTED_API_COMMIT) { throw new OracleValidationError('cases manifest target does not match the pinned Obsidian/API versions') } nonEmptyString(root.locale, 'cases manifest.locale') nonEmptyString(root.timezone, 'cases manifest.timezone') if (!Number.isInteger(root.seed)) throw new OracleValidationError('cases manifest.seed must be an integer') if (!Array.isArray(root.cases) || root.cases.length === 0) { throw new OracleValidationError('cases manifest.cases must be a non-empty array') } const ids = new Set() const viewNames = new Set() const coverage = new Set() const canonicalInputs = [] for (const [index, rawCase] of root.cases.entries()) { const testCase = object(rawCase, `cases[${index}]`) exactKeys(testCase, ['id', 'inputPath', 'description', 'coverage', 'capture', 'probes', 'generator', 'shrunk'], `case ${index}`) const id = nonEmptyString(testCase.id, `cases[${index}].id`) if (!/^[a-z][a-z0-9-]*$/.test(id)) throw new OracleValidationError(`Invalid case id '${id}'`) if (ids.has(id)) throw new OracleValidationError(`Duplicate case id '${id}'`) ids.add(id) nonEmptyString(testCase.description, `case '${id}' description`) caseInputPath(testCase, index) for (const tag of stringArray(testCase.coverage, `case '${id}' coverage`)) { if (!tag) throw new OracleValidationError(`case '${id}' has an empty coverage tag`) coverage.add(tag) } const capture = object(testCase.capture, `case '${id}' capture`) exactKeys(capture, ['viewName', 'preserveRowOrder', 'allowEmptyRows'], `case '${id}' capture`) const viewName = nonEmptyString(capture.viewName, `case '${id}' capture.viewName`) if (viewNames.has(viewName)) throw new OracleValidationError(`Duplicate capture viewName '${viewName}'`) viewNames.add(viewName) if (capture.preserveRowOrder !== undefined && typeof capture.preserveRowOrder !== 'boolean') { throw new OracleValidationError(`case '${id}' capture.preserveRowOrder must be boolean`) } validateEmptyRowAllowance(capture.allowEmptyRows, `case '${id}' capture.allowEmptyRows`) const probes = object(testCase.probes, `case '${id}' probes`) exactKeys(probes, ['properties', 'summaries'], `case '${id}' probes`) if (!Array.isArray(probes.properties)) throw new OracleValidationError(`case '${id}' probes.properties must be an array`) const properties = probes.properties.map((value, probeIndex) => probe(value, `case '${id}' property probe ${probeIndex}`)) if ((properties.length === 0 && testCase.capture.allowEmptyRows === undefined) || new Set(properties.map(item => item.property)).size !== properties.length) { throw new OracleValidationError(`case '${id}' probes.properties must be non-empty and unique`) } if (probes.summaries !== undefined) { if (!Array.isArray(probes.summaries)) throw new OracleValidationError(`case '${id}' probes.summaries must be an array`) for (const [probeIndex, probeValue] of probes.summaries.entries()) { const probe = object(probeValue, `case '${id}' summary probe ${probeIndex}`) exactKeys(probeValue, ['property', 'summary', 'outcome'], `case '${id}' summary probe ${probeIndex}`) nonEmptyString(probe.property, `case '${id}' summary property`) nonEmptyString(probe.summary, `case '${id}' summary name`) if (!['success', 'error', 'null'].includes(probe.outcome)) throw new OracleValidationError(`case '${id}' summary probe ${probeIndex}.outcome must be success, error, or null`) } } validateEmptyCaseProbes(testCase, `case '${id}'`) optionalMetadata(testCase.generator, `case '${id}' generator`, ['name', 'seed']) optionalMetadata(testCase.shrunk, `case '${id}' shrunk`, ['reason']) const input = caseInputPath(testCase, index) const bytes = fs.readFileSync(input.absolute) validateRawBase(bytes, `case '${id}' input`, viewName, testCase.coverage) canonicalInputs.push({ ...input, bytes }) } if (coverage.size === 0) throw new OracleValidationError('cases manifest must declare coverage tags') Object.defineProperty(root, '__canonicalInputs', { value: canonicalInputs }) return root}
function validateInventory(value, cases) { const root = object(value, 'coverage inventory') exactKeys(root, ['schemaVersion', 'requiredFeatureIds', 'features'], 'coverage inventory') if (root.schemaVersion !== 1 || !Array.isArray(root.features) || root.features.length === 0) { throw new OracleValidationError('coverage inventory must be a non-empty schema-1 feature list') } const requiredFeatureIds = stringArray(root.requiredFeatureIds, 'coverage inventory.requiredFeatureIds') if (requiredFeatureIds.length === 0 || new Set(requiredFeatureIds).size !== requiredFeatureIds.length) { throw new OracleValidationError('coverage inventory.requiredFeatureIds must be non-empty and unique') } const caseIds = new Set(cases.cases.map(testCase => testCase.id)) const coverageTags = new Set(cases.cases.flatMap(testCase => testCase.coverage)) const inventoryCases = new Set() const inventoryTags = new Set() const featureIds = new Set() for (const [index, rawFeature] of root.features.entries()) { const feature = object(rawFeature, `coverage feature ${index}`) exactKeys(feature, ['id', 'status', 'caseIds', 'coverageTags', 'source'], `coverage feature ${index}`) const id = nonEmptyString(feature.id, `coverage feature ${index}.id`) if (featureIds.has(id)) throw new OracleValidationError(`duplicate coverage feature '${id}'`) featureIds.add(id) if (!['supported', 'discovery', 'deferred'].includes(feature.status)) throw new OracleValidationError(`coverage feature '${id}' has an invalid status`) const featureCaseIds = stringArray(feature.caseIds, `coverage feature '${id}'.caseIds`) const featureTags = stringArray(feature.coverageTags, `coverage feature '${id}'.coverageTags`) if (featureCaseIds.length === 0 || new Set(featureCaseIds).size !== featureCaseIds.length || featureTags.length === 0 || new Set(featureTags).size !== featureTags.length) { throw new OracleValidationError(`coverage feature '${id}' mappings must be non-empty and unique`) } const source = object(feature.source, `coverage feature '${id}'.source`) exactKeys(source, ['url', 'versionNote'], `coverage feature '${id}'.source`) nonEmptyString(source.url, `coverage feature '${id}'.source.url`) nonEmptyString(source.versionNote, `coverage feature '${id}'.source.versionNote`) for (const caseId of featureCaseIds) { if (!caseIds.has(caseId)) throw new OracleValidationError(`coverage feature '${id}' references unknown case '${caseId}'`) const mappedCase = cases.cases.find(testCase => testCase.id === caseId) if (!mappedCase.coverage.includes(id)) { throw new OracleValidationError(`coverage feature '${id}' case '${caseId}' must include the feature ID in case.coverage`) } const discoveryCase = mappedCase.coverage.length > 0 && mappedCase.coverage.every(tag => tag.endsWith('.discovery')) if (feature.status === 'supported' && discoveryCase) { throw new OracleValidationError(`supported coverage feature '${id}' may not use discovery case '${caseId}'`) } inventoryCases.add(caseId) } for (const tag of featureTags) { if (!coverageTags.has(tag)) throw new OracleValidationError(`coverage feature '${id}' references unknown tag '${tag}'`) inventoryTags.add(tag) } } for (const requiredId of requiredFeatureIds) if (!featureIds.has(requiredId)) throw new OracleValidationError(`required coverage feature '${requiredId}' is missing`) if (inventoryCases.size !== caseIds.size) throw new OracleValidationError('coverage inventory does not cover every case') if (inventoryTags.size !== coverageTags.size) throw new OracleValidationError('coverage inventory does not declare every case coverage tag') return root}
function validateOfficialSyntaxInventory(value, cases) { const root = object(value, 'official syntax inventory') exactKeys(root, ['schemaVersion', 'target', 'symbols'], 'official syntax inventory') if (root.schemaVersion !== 1 || !Array.isArray(root.symbols) || root.symbols.length === 0) { throw new OracleValidationError('official syntax inventory must be a non-empty schema-1 symbol list') } const actualHash = sha256(fs.readFileSync(OFFICIAL_SYNTAX_INVENTORY_PATH)) if (actualHash !== OFFICIAL_SYNTAX_INVENTORY_SHA256) { throw new OracleValidationError('official syntax inventory hash does not match the pinned harness hash') } const caseIds = new Set(cases.cases.map(testCase => testCase.id)) const ids = new Set() for (const [index, rawSymbol] of root.symbols.entries()) { const symbol = object(rawSymbol, `official syntax symbol ${index}`) exactKeys(symbol, ['id', 'status', 'caseIds', 'source', 'witness'], `official syntax symbol ${index}`) const id = nonEmptyString(symbol.id, `official syntax symbol ${index}.id`) if (ids.has(id)) throw new OracleValidationError(`duplicate official syntax symbol '${id}'`) ids.add(id) if (!['supported', 'discovery', 'deferred'].includes(symbol.status)) { throw new OracleValidationError(`official syntax symbol '${id}' has an invalid status`) } const symbolCases = stringArray(symbol.caseIds, `official syntax symbol '${id}'.caseIds`) if (symbol.status === 'supported' && symbolCases.length === 0) { throw new OracleValidationError(`supported official syntax symbol '${id}' must map to cases`) } for (const caseId of symbolCases) if (!caseIds.has(caseId)) { throw new OracleValidationError(`official syntax symbol '${id}' references unknown case '${caseId}'`) } if (symbol.status === 'supported') { const witness = object(symbol.witness, `official syntax symbol '${id}'.witness`) exactKeys(witness, ['caseId', 'yamlPath', 'syntax', 'observation'], `official syntax symbol '${id}'.witness`) if (!symbolCases.includes(witness.caseId)) throw new OracleValidationError(`official syntax symbol '${id}' witness must name a mapped case`) if (!Array.isArray(witness.yamlPath) || witness.yamlPath.length === 0 || !witness.yamlPath.every(part => (typeof part === 'string' && part.length > 0) || Number.isInteger(part))) { throw new OracleValidationError(`official syntax symbol '${id}'.witness.yamlPath must be a non-empty YAML path`) } nonEmptyString(witness.syntax, `official syntax symbol '${id}'.witness.syntax`) const witnessCase = cases.cases.find(testCase => testCase.id === witness.caseId) if (!witnessCase.coverage.includes(id)) throw new OracleValidationError(`official syntax symbol '${id}' witness case must carry the feature ID in case.coverage`) const observation = object(witness.observation, `official syntax symbol '${id}'.witness.observation`) const observationKind = nonEmptyString(observation.kind, `official syntax symbol '${id}'.witness.observation.kind`) if (observationKind === 'property') { exactKeys(observation, ['kind', 'property'], `official syntax symbol '${id}'.witness.observation`) nonEmptyString(observation.property, `official syntax symbol '${id}'.witness.observation.property`) if (!witnessCase.probes.properties.some(item => item.property === observation.property)) throw new OracleValidationError(`official syntax symbol '${id}' witness property probe is not declared by its case`) } else if (observationKind === 'rows') { exactKeys(observation, ['kind'], `official syntax symbol '${id}'.witness.observation`) } else if (observationKind === 'summary') { exactKeys(observation, ['kind', 'property', 'summary'], `official syntax symbol '${id}'.witness.observation`) if (!(witnessCase.probes.summaries ?? []).some(item => item.property === observation.property && item.summary === observation.summary)) throw new OracleValidationError(`official syntax symbol '${id}' witness summary probe is not declared by its case`) } else throw new OracleValidationError(`official syntax symbol '${id}' witness has unknown observation kind`) const parsed = validateRawBase(fs.readFileSync(caseInputPath(witnessCase, 0).absolute), `official syntax symbol '${id}' witness input`, witnessCase.capture.viewName, witnessCase.coverage) let node = parsed for (const part of witness.yamlPath) { if (node === null || node === undefined || (typeof part === 'string' && !Object.prototype.hasOwnProperty.call(node, part))) { throw new OracleValidationError(`official syntax symbol '${id}' witness YAML path does not resolve`) } node = node[part] } const parent = witness.yamlPath.length > 1 ? witness.yamlPath.slice(0, -1).reduce((current, part) => current?.[part], parsed) : parsed const key = witness.yamlPath.at(-1) const containsWitnessSyntax = value => typeof value === 'string' ? (value === witness.syntax || value.includes(witness.syntax)) : value && typeof value === 'object' && Object.values(value).some(containsWitnessSyntax) if (!containsWitnessSyntax(node)) { throw new OracleValidationError(`official syntax symbol '${id}' witness syntax does not exactly match its decoded YAML scalar`) } } else if (symbol.witness !== undefined) throw new OracleValidationError(`non-supported official syntax symbol '${id}' may not declare a witness`) const source = object(symbol.source, `official syntax symbol '${id}'.source`) exactKeys(source, ['url', 'versionNote'], `official syntax symbol '${id}'.source`) nonEmptyString(source.url, `official syntax symbol '${id}'.source.url`) nonEmptyString(source.versionNote, `official syntax symbol '${id}'.source.versionNote`) } return root}
function validateRelations(value, cases) { const root = object(value, 'relations manifest') exactKeys(root, ['schemaVersion', 'relations'], 'relations manifest') if (root.schemaVersion !== 1 || !Array.isArray(root.relations) || root.relations.length < 10) { throw new OracleValidationError('relations manifest must contain at least 10 schema-1 relations') } const byId = new Map(cases.cases.map(testCase => [testCase.id, testCase])) const ids = new Set() for (const [index, rawRelation] of root.relations.entries()) { const relation = object(rawRelation, `relation ${index}`) const allowedKeys = ['id', 'kind', 'left', 'right', 'leftProperty', 'rightProperty', 'requireNonEmpty'] exactKeys(relation, allowedKeys, `relation ${index}`) const id = nonEmptyString(relation.id, `relation ${index}.id`) if (ids.has(id)) throw new OracleValidationError(`duplicate relation id '${id}'`) ids.add(id) const kinds = new Set(['property-value-equality', 'property-value-inequality', 'row-membership-equality', 'row-membership-inequality', 'full-result-equality']) if (!kinds.has(relation.kind)) throw new OracleValidationError(`relation '${id}' has an unknown kind`) const propertyKind = relation.kind.startsWith('property-value-') if (propertyKind) { nonEmptyString(relation.leftProperty, `relation '${id}'.leftProperty`) nonEmptyString(relation.rightProperty, `relation '${id}'.rightProperty`) } else if (relation.leftProperty !== undefined || relation.rightProperty !== undefined) { throw new OracleValidationError(`relation '${id}' may not contain property projections`) } if (relation.requireNonEmpty !== undefined && relation.kind !== 'row-membership-equality') { throw new OracleValidationError(`relation '${id}' may only require non-empty membership for row equality`) } if (relation.kind === 'row-membership-equality' && relation.requireNonEmpty !== undefined && typeof relation.requireNonEmpty !== 'boolean') { throw new OracleValidationError(`relation '${id}'.requireNonEmpty must be boolean`) } const endpoints = [relation.left, relation.right] const inputPaths = new Set() for (const [endpointIndex, rawEndpoint] of endpoints.entries()) { const endpoint = object(rawEndpoint, `relation '${id}' endpoint ${endpointIndex}`) exactKeys(endpoint, ['caseId', 'inputPath'], `relation '${id}' endpoint ${endpointIndex}`) const caseId = nonEmptyString(endpoint.caseId, `relation '${id}' caseId`) const testCase = byId.get(caseId) if (!testCase) throw new OracleValidationError(`relation '${id}' references unknown case '${caseId}'`) if (endpoint.inputPath !== testCase.inputPath) throw new OracleValidationError(`relation '${id}' inputPath does not match case '${caseId}'`) const property = endpointIndex === 0 ? relation.leftProperty : relation.rightProperty if (propertyKind && !testCase.probes.properties.some(probe => probe.property === property)) throw new OracleValidationError(`relation '${id}' property is not probed by '${caseId}'`) inputPaths.add(endpoint.inputPath) } if (endpoints[0].caseId === endpoints[1].caseId || inputPaths.size !== 2) { throw new OracleValidationError(`relation '${id}' must reference two independent raw files`) } } return root}
function relationCaseMap(raw) { return new Map(raw.cases.map(testCase => [testCase.id, testCase]))}
function rowValueMap(rawCase, property) { return new Map(rawCase.rows.map(row => [row.file, property === undefined ? null : row.values[property] ?? null]))}
function relationProjection(raw, relation) { const left = raw.get(relation.left.caseId) const right = raw.get(relation.right.caseId) if (!left || !right) throw new OracleValidationError(`relation '${relation.id}' result case is missing`) if (relation.kind.startsWith('property-value-')) { return { left: rowValueMap(left, relation.leftProperty), right: rowValueMap(right, relation.rightProperty) } } if (relation.kind.startsWith('row-membership-')) { return { left: new Set(left.rows.map(row => row.file)), right: new Set(right.rows.map(row => row.file)) } } const stripId = value => Object.fromEntries(Object.entries(value).filter(([key]) => key !== 'id')) return { left: stripId(left), right: stripId(right) }}
function equalProjection(left, right) { if (left instanceof Map && right instanceof Map) { if (left.size !== right.size || [...left.keys()].some(key => !right.has(key))) return false return [...left].every(([key, value]) => canonicalJson(value) === canonicalJson(right.get(key))) } if (left instanceof Set && right instanceof Set) { return left.size === right.size && [...left].every(value => right.has(value)) } return canonicalJson(left) === canonicalJson(right)}
function isSuccessfulValue(value) { return Boolean(value && typeof value === 'object' && value.rendered?.status === 'ok' && value.equalsNull === false)}
function containsSuccessfulValue(value) { if (isSuccessfulValue(value)) return true if (Array.isArray(value)) return value.some(containsSuccessfulValue) if (!value || typeof value !== 'object') return false return Object.values(value).some(containsSuccessfulValue)}
function projectionHasSuccessfulValue(projection) { if (projection instanceof Map) return [...projection.values()].some(isSuccessfulValue) return containsSuccessfulValue(projection)}
function normalizedValueHasRenderedError(value) { if (value === null) return false if (value && typeof value === 'object') { if (value.rendered?.status === 'error') return true if (Array.isArray(value)) return value.some(normalizedValueHasRenderedError) return Object.values(value).some(normalizedValueHasRenderedError) } return false}
function validateRelationsAgainstRaw(raw, relations) { const results = relationCaseMap(raw) for (const relation of relations.relations) { const projection = relationProjection(results, relation) if (relation.kind.startsWith('property-value-') && (!projectionHasSuccessfulValue(projection.left) || !projectionHasSuccessfulValue(projection.right))) { throw new OracleValidationError(`relation '${relation.id}' has no compared successful non-null value`) } if (relation.kind === 'full-result-equality' && (!projectionHasSuccessfulValue(projection.left) || !projectionHasSuccessfulValue(projection.right))) { throw new OracleValidationError(`relation '${relation.id}' has no compared successful non-null value`) } if (relation.kind === 'row-membership-equality' && relation.requireNonEmpty && (projection.left.size === 0 || projection.right.size === 0)) { throw new OracleValidationError(`relation '${relation.id}' requires non-empty row membership`) } const equal = equalProjection(projection.left, projection.right) const expectsEqual = relation.kind.endsWith('-equality') if (equal !== expectsEqual) throw new OracleValidationError(`relation '${relation.id}' was violated`) }}
function sha256(value) { return crypto.createHash('sha256').update(value).digest('hex')}
function harnessSourceSha256() { const files = [ SCRIPT_PATH, CASES_PATH, ...listFiles(CASE_INPUT_ROOT).map(relative => path.join(CASE_INPUT_ROOT, relative)), RELATIONS_PATH, INVENTORY_PATH, OFFICIAL_SYNTAX_INVENTORY_PATH, ...listFiles(FIXTURE_ROOT).map(relative => path.join(FIXTURE_ROOT, relative)), ...listFiles(PLUGIN_ROOT).map(relative => path.join(PLUGIN_ROOT, relative)), ].sort() const hash = crypto.createHash('sha256') for (const file of files) { hash.update(path.relative(ROOT, file).split(path.sep).join('/')) hash.update('\0') hash.update(fs.readFileSync(file)) hash.update('\0') } return hash.digest('hex')}
function validateNormalizedValue(value, label) { const candidate = object(value, label) exactKeys(candidate, ['constructorName', 'type', 'text', 'truthy', 'equalsNull', 'looselyEqualsNull', 'equalsSelf', 'looselyEqualsSelf', 'rendered', 'items'], label) for (const key of ['constructorName', 'type', 'text']) { if (typeof candidate[key] !== 'string') throw new OracleValidationError(`${label}.${key} must be a string`) } nonEmptyString(candidate.type, `${label}.type`) for (const key of ['truthy', 'equalsNull', 'looselyEqualsNull', 'equalsSelf', 'looselyEqualsSelf']) { if (typeof candidate[key] !== 'boolean') throw new OracleValidationError(`${label}.${key} must be boolean`) } const rendered = object(candidate.rendered, `${label}.rendered`) if (rendered.status === 'ok') exactKeys(rendered, ['status', 'html'], `${label}.rendered`) if (rendered.status === 'error') exactKeys(rendered, ['status', 'name', 'message'], `${label}.rendered`) if (rendered.status === 'ok') { if (typeof rendered.html !== 'string') throw new OracleValidationError(`${label}.rendered.html must be a string`) } else if (rendered.status === 'error') { nonEmptyString(rendered.name, `${label}.rendered.name`) if (typeof rendered.message !== 'string') throw new OracleValidationError(`${label}.rendered.message must be a string`) } else throw new OracleValidationError(`${label}.rendered.status must be ok or error`) if (candidate.items !== undefined) { if (!Array.isArray(candidate.items)) throw new OracleValidationError(`${label}.items must be an array`) candidate.items.forEach((item, index) => validateNormalizedValue(item, `${label}.items[${index}]`)) }}
function observedOutcome(value) { if (value === null) return 'null' if (typeof value?.type === 'string' && value.type.toLowerCase() === 'error') return 'error' if (value?.rendered?.status === 'error') return 'invalid' if (value?.rendered?.status === 'ok' && value.equalsNull === false) return 'success' if (value?.equalsNull === true) return 'null' return 'invalid'}
function validateProbeOutcome(value, expected, label) { if (expected === 'error' && value?.type?.toLowerCase() !== 'error' && value?.equalsNull !== true) { throw new OracleValidationError(`${label} rendered an error, but normalized runtime type is '${value?.type ?? ''}', not Error`) } const actual = observedOutcome(value) if (expected === 'success' && (actual === 'null' || actual === 'error')) return if (expected === 'error' && actual === 'null') return if (actual !== expected) throw new OracleValidationError(`${label} expected ${expected} outcome, got ${actual}`)}
function validateRawResults(value, cases, run, relations) { const root = object(value, 'raw results') exactKeys(root, ['schemaVersion', 'normalizationVersion', 'environment', 'cases'], 'raw results') if (root.schemaVersion !== 1 || root.normalizationVersion !== 1) { throw new OracleValidationError('raw result versions must both equal 1') } const environment = object(root.environment, 'raw results.environment') exactKeys(environment, ['targetObsidianVersion', 'runtimeApiVersion', 'apiCommit', 'fixtureVaultSha256', 'runId', 'pluginSha256', 'apiSnapshotSha256', 'harnessSourceSha256', 'locale', 'timezone', 'seed'], 'raw results.environment') const expectations = { targetObsidianVersion: cases.target.obsidianVersion, runtimeApiVersion: cases.target.obsidianVersion, apiCommit: cases.target.apiCommit, fixtureVaultSha256: run.fixtureVaultSha256, runId: run.runId, pluginSha256: run.pluginSha256, apiSnapshotSha256: run.apiSnapshotSha256, harnessSourceSha256: run.harnessSourceSha256, locale: cases.locale, timezone: cases.timezone, seed: cases.seed, } for (const [key, expected] of Object.entries(expectations)) { if (environment[key] !== expected) { throw new OracleValidationError(`raw results.environment.${key} expected ${JSON.stringify(expected)}, got ${JSON.stringify(environment[key])}`) } } if (!Array.isArray(root.cases)) throw new OracleValidationError('raw results.cases must be an array') const expectedIds = cases.cases.map(testCase => testCase.id) const actualIds = root.cases.map((rawCase, index) => nonEmptyString(object(rawCase, `raw case ${index}`).id, `raw case ${index}.id`)) if (JSON.stringify(actualIds) !== JSON.stringify(expectedIds)) { throw new OracleValidationError(`raw result case IDs/order do not match prepared cases`) } for (const [caseIndex, rawCaseValue] of root.cases.entries()) { const sourceCase = cases.cases[caseIndex] const rawCase = object(rawCaseValue, 'raw case') exactKeys(rawCase, ['id', 'properties', 'allProperties', 'rows', 'groups', 'summaries'], `raw case '${rawCase.id ?? caseIndex}'`) const properties = stringArray(rawCase.properties, `raw case '${rawCase.id}' properties`) const allProperties = stringArray(rawCase.allProperties, `raw case '${rawCase.id}' allProperties`) if (new Set(properties).size !== properties.length || new Set(allProperties).size !== allProperties.length) { throw new OracleValidationError(`raw case '${rawCase.id}' properties contain duplicates`) } if (JSON.stringify(allProperties) !== JSON.stringify([...allProperties].sort())) { throw new OracleValidationError(`raw case '${rawCase.id}' allProperties are not normalized`) } if (!Array.isArray(rawCase.rows)) throw new OracleValidationError(`raw case '${rawCase.id}' rows must be an array`) if (rawCase.rows.length === 0 && sourceCase.capture.allowEmptyRows === undefined) { throw new OracleValidationError(`raw case '${rawCase.id}' must contain at least one row`) } if (rawCase.rows.length === 0 && sourceCase.capture.allowEmptyRows !== undefined) { if (rawCase.groups.length !== 0 || Object.keys(rawCase.summaries ?? {}).length !== 0) { throw new OracleValidationError(`raw case '${rawCase.id}' expected-empty capture must contain no groups or summaries`) } } const actualFiles = rawCase.rows.map(row => object(row, `raw case '${rawCase.id}' row`).file) const fixtureFiles = run.cases?.[caseIndex]?.fixtureFiles ?? run.fixtureFiles if (new Set(actualFiles).size !== actualFiles.length || actualFiles.some(file => !fixtureFiles.includes(file))) { throw new OracleValidationError(`raw case '${rawCase.id}' rows contain duplicate or unknown fixture files`) } for (const [rowIndex, rowValue] of rawCase.rows.entries()) { const row = object(rowValue, `raw case '${rawCase.id}' row ${rowIndex}`) exactKeys(row, ['file', 'values'], `raw case '${rawCase.id}' row ${rowIndex}`) nonEmptyString(row.file, `raw case '${rawCase.id}' row file`) const values = object(row.values, `raw case '${rawCase.id}' row values`) const valueKeys = Object.keys(values).sort() const expectedProperties = sourceCase.probes.properties.map(item => item.property) if (JSON.stringify(valueKeys) !== JSON.stringify([...expectedProperties].sort())) { throw new OracleValidationError(`raw case '${rawCase.id}' row values do not match requested properties`) } for (const [property, normalized] of Object.entries(values)) { if (normalized !== null) validateNormalizedValue(normalized, `raw case '${rawCase.id}' value '${property}'`) const expected = sourceCase.probes.properties.find(item => item.property === property).outcome if (normalized !== null) validateProbeOutcome(normalized, expected, `raw case '${rawCase.id}' property '${property}'`) else if (expected !== 'null' && expected !== 'success' && expected !== 'error') throw new OracleValidationError(`raw case '${rawCase.id}' property '${property}' expected ${expected} outcome, got null`) } } for (const { property } of sourceCase.probes.properties) { if (!allProperties.includes(property)) { throw new OracleValidationError(`raw case '${rawCase.id}' allProperties omits requested '${property}'`) } } if (!Array.isArray(rawCase.groups) || (rawCase.groups.length === 0 && rawCase.rows.length > 0)) throw new OracleValidationError(`raw case '${rawCase.id}' groups must be non-empty`) const groupedFiles = [] for (const [groupIndex, groupValue] of rawCase.groups.entries()) { const group = object(groupValue, `raw case '${rawCase.id}' group ${groupIndex}`) exactKeys(group, ['key', 'files'], `raw case '${rawCase.id}' group ${groupIndex}`) if (group.key !== null) validateNormalizedValue(group.key, `raw case '${rawCase.id}' group key`) const files = stringArray(group.files, `raw case '${rawCase.id}' group files`) groupedFiles.push(...files) } if (new Set(groupedFiles).size !== groupedFiles.length || JSON.stringify([...groupedFiles].sort()) !== JSON.stringify([...actualFiles].sort())) { throw new OracleValidationError(`raw case '${rawCase.id}' groups do not partition result rows`) } const preserveRowOrder = run.cases?.[caseIndex]?.preserveRowOrder ?? false if (preserveRowOrder) { if (JSON.stringify(groupedFiles) !== JSON.stringify(actualFiles)) { throw new OracleValidationError(`raw case '${rawCase.id}' does not preserve row/group order`) } } else { if (JSON.stringify(actualFiles) !== JSON.stringify([...actualFiles].sort())) { throw new OracleValidationError(`raw case '${rawCase.id}' rows are not canonically ordered`) } for (const group of rawCase.groups) { if (JSON.stringify(group.files) !== JSON.stringify([...group.files].sort())) { throw new OracleValidationError(`raw case '${rawCase.id}' group files are not canonically ordered`) } } const groupKeys = rawCase.groups.map(group => JSON.stringify(group.key)) if (JSON.stringify(groupKeys) !== JSON.stringify([...groupKeys].sort())) { throw new OracleValidationError(`raw case '${rawCase.id}' groups are not canonically ordered`) } } const summaries = object(rawCase.summaries, `raw case '${rawCase.id}' summaries`) const expectedSummaryKeys = (sourceCase.probes.summaries ?? []).map(probe => `${probe.property}:${probe.summary}`) if (JSON.stringify(Object.keys(summaries).sort()) !== JSON.stringify(expectedSummaryKeys.sort())) { throw new OracleValidationError(`raw case '${rawCase.id}' summaries do not match requested probes`) } for (const [key, normalized] of Object.entries(summaries)) { validateNormalizedValue(normalized, `raw case '${rawCase.id}' summary '${key}'`) const expected = sourceCase.probes.summaries.find(probe => `${probe.property}:${probe.summary}` === key).outcome validateProbeOutcome(normalized, expected, `raw case '${rawCase.id}' summary '${key}'`) } } validateRelationsAgainstRaw(root, relations) return root}
function validatePreparedRun(value, cases, stageRoot) { const run = object(value, 'prepared run manifest') exactKeys(run, ['schemaVersion', 'normalizationVersion', 'target', 'fixtureVaultSha256', 'runId', 'pluginSha256', 'pluginPromotable', 'apiSnapshotSha256', 'harnessSourceSha256', 'locale', 'timezone', 'seed', 'cases', 'artifacts'], 'prepared run manifest') if (run.schemaVersion !== 1 || run.normalizationVersion !== 1) throw new OracleValidationError('prepared run versions must equal 1') if (canonicalJson(run.target) !== canonicalJson(cases.target) || run.locale !== cases.locale || run.timezone !== cases.timezone || run.seed !== cases.seed) { throw new OracleValidationError('prepared run does not match the source case manifest') } nonEmptyString(run.runId, 'prepared run.runId') if (typeof run.pluginPromotable !== 'boolean') throw new OracleValidationError('prepared run.pluginPromotable must be boolean') if (run.apiSnapshotSha256 !== sha256(fs.readFileSync(API_SNAPSHOT_PATH))) { throw new OracleValidationError('prepared run API snapshot hash does not match the vendored declaration') } if (run.harnessSourceSha256 !== harnessSourceSha256()) { throw new OracleValidationError('prepared run harness source hash is stale') } if (!Array.isArray(run.artifacts) || run.artifacts.length === 0) throw new OracleValidationError('prepared run.artifacts must be non-empty') const artifactPaths = [] for (const [index, rawArtifact] of run.artifacts.entries()) { const artifact = object(rawArtifact, `prepared run artifact ${index}`) exactKeys(artifact, ['path', 'sha256'], `prepared run artifact ${index}`) const relative = nonEmptyString(artifact.path, `prepared run artifact ${index}.path`) if (relative.includes('\\') || path.isAbsolute(relative) || relative.split('/').includes('..')) { throw new OracleValidationError(`prepared run artifact path '${relative}' is not canonical`) } const absolute = path.join(stageRoot, ...relative.split('/')) if (!fs.existsSync(absolute) || sha256(fs.readFileSync(absolute)) !== artifact.sha256) { throw new OracleValidationError(`prepared artifact '${relative}' is missing or changed`) } artifactPaths.push(relative) } if (new Set(artifactPaths).size !== artifactPaths.length) throw new OracleValidationError('prepared run has duplicate artifact paths') const aggregate = sha256(run.artifacts.map(artifact => `${artifact.path}\0${artifact.sha256}\0`).join('')) if (aggregate !== run.fixtureVaultSha256) throw new OracleValidationError('prepared run fixture hash does not match its artifacts') const pluginArtifact = run.artifacts.find(artifact => artifact.path === `.obsidian/plugins/${VIEW_TYPE}/main.js`) if (!pluginArtifact || pluginArtifact.sha256 !== run.pluginSha256) throw new OracleValidationError('prepared run plugin hash is incoherent') const fixtureFiles = listFiles(stageRoot, path.join('_bases_oracle', 'rows')) .map(relative => relative.split(path.sep).join('/')) .sort() const expectedCases = cases.cases.map(testCase => ({ id: testCase.id, path: `_bases_oracle/cases/${testCase.inputPath}`, probes: testCase.probes, viewName: testCase.capture.viewName, fixtureFiles, preserveRowOrder: testCase.capture.preserveRowOrder ?? false, })) if (canonicalJson(run.cases) !== canonicalJson(expectedCases)) throw new OracleValidationError('prepared run cases do not match source cases') for (const [index, testCase] of cases.cases.entries()) { const stagedPath = path.join(stageRoot, '_bases_oracle', 'cases', testCase.inputPath) const stagedBytes = fs.readFileSync(stagedPath) if (!stagedBytes.equals(cases.__canonicalInputs[index].bytes)) { throw new OracleValidationError(`prepared Base '${testCase.id}' differs from the canonical source bytes`) } } return run}
function canonical(value) { if (Array.isArray(value)) return value.map(canonical) if (!value || typeof value !== 'object') return value return Object.fromEntries(Object.keys(value).sort().map(key => [key, canonical(value[key])]))}
function canonicalJson(value) { return `${JSON.stringify(canonical(value), null, 2)}\n`}
function freshDefaultPluginSha256() { if (!fs.existsSync(VITE_BIN)) throw new OracleValidationError(`Missing Vite build '${VITE_BIN}'`) const result = spawnSync(process.execPath, [VITE_BIN, 'build', '--config', VITE_CONFIG], { cwd: ROOT, encoding: 'utf8', }) if (result.status !== 0) { throw new OracleValidationError(`Unable to build the default fixture plugin: ${result.stderr || result.stdout}`) } if (!fs.existsSync(DEFAULT_PLUGIN_BUILD)) throw new OracleValidationError('Default fixture plugin build did not produce main.js') return sha256(fs.readFileSync(DEFAULT_PLUGIN_BUILD))}
function canonicalArtifactBytes(relative, cases) { if (relative === '_bases_oracle/source-cases.json') return fs.readFileSync(CASES_PATH) if (relative.startsWith('_bases_oracle/cases/')) { const inputPath = relative.slice('_bases_oracle/cases/'.length) const sourceCase = cases.cases.find(testCase => testCase.inputPath === inputPath) if (!sourceCase) return null return fs.readFileSync(caseInputPath(sourceCase, 0).absolute) } if (relative.startsWith('_bases_oracle/rows/')) { const fixturePath = path.join(FIXTURE_ROOT, 'rows', relative.slice('_bases_oracle/rows/'.length)) return fs.existsSync(fixturePath) ? fs.readFileSync(fixturePath) : null } if (relative === `.obsidian/plugins/${VIEW_TYPE}/manifest.json`) return fs.readFileSync(path.join(PLUGIN_ROOT, 'manifest.json')) if (relative === `.obsidian/plugins/${VIEW_TYPE}/main.js`) return fs.existsSync(DEFAULT_PLUGIN_BUILD) ? fs.readFileSync(DEFAULT_PLUGIN_BUILD) : null if (relative === '.obsidian/community-plugins.json') return Buffer.from(`${JSON.stringify([VIEW_TYPE], null, 2)}\n`) if (relative === '.obsidian/core-plugins.json') return Buffer.from(`${JSON.stringify(['bases'], null, 2)}\n`) return null}
function validateCanonicalArtifacts(artifacts, cases, label) { const expectedPaths = new Set([ '_bases_oracle/source-cases.json', ...listFiles(path.join(FIXTURE_ROOT, 'rows')).map(relative => `_bases_oracle/rows/${relative.split(path.sep).join('/')}`), ...cases.cases.map(testCase => `_bases_oracle/cases/${testCase.inputPath}`), `.obsidian/plugins/${VIEW_TYPE}/manifest.json`, `.obsidian/plugins/${VIEW_TYPE}/main.js`, '.obsidian/community-plugins.json', '.obsidian/core-plugins.json', ]) if (artifacts.size !== expectedPaths.size || [...expectedPaths].some(relative => !artifacts.has(relative))) { throw new OracleValidationError(`${label} artifact inventory does not match canonical sources`) } for (const [relative, actualSha] of artifacts) { const expected = canonicalArtifactBytes(relative, cases) if (!expected || sha256(expected) !== actualSha) throw new OracleValidationError(`${label} artifact '${relative}' does not match the canonical source or generated expected bytes`) }}
function listFiles(root, prefix = '') { const directory = path.join(root, prefix) if (!fs.existsSync(directory)) return [] return fs.readdirSync(directory, { withFileTypes: true }).flatMap(entry => { const relative = path.join(prefix, entry.name) return entry.isDirectory() ? listFiles(root, relative) : [relative] })}
function validateStagePath(outPath) { const resolved = path.resolve(outPath) if (resolved === path.resolve(DEFAULT_STAGE)) return resolved const relativeToTmp = path.relative(os.tmpdir(), resolved) const [temporaryRoot, leaf, ...rest] = relativeToTmp.split(path.sep) if (relativeToTmp.startsWith('..') || path.isAbsolute(relativeToTmp) || !temporaryRoot?.startsWith('bases-oracle-') || leaf !== 'vault' || rest.length > 0) { throw new OracleValidationError( `Refusing unsafe staging path '${outPath}'; use the default or ${path.join(os.tmpdir(), 'bases-oracle-<id>', 'vault')}`, ) } return resolved}
function prepare(outPath) { outPath = validateStagePath(outPath) const cases = validateCases(readJson(CASES_PATH, 'cases manifest')) const relations = validateRelations(readJson(RELATIONS_PATH, 'relations manifest'), cases) validateInventory(readJson(INVENTORY_PATH, 'coverage inventory'), cases) const overridePluginBuild = process.env.BASES_ORACLE_PLUGIN_BUILD const pluginBuild = overridePluginBuild ?? DEFAULT_PLUGIN_BUILD if (!overridePluginBuild) freshDefaultPluginSha256() if (!fs.existsSync(pluginBuild)) { throw new OracleValidationError(`Missing fixture plugin build '${pluginBuild}'. Run npm run bases:oracle:build first.`) } fs.rmSync(outPath, { recursive: true, force: true }) fs.mkdirSync(path.join(outPath, '_bases_oracle', 'rows'), { recursive: true }) fs.mkdirSync(path.join(outPath, '_bases_oracle', 'cases'), { recursive: true }) fs.mkdirSync(path.join(outPath, '.obsidian', 'plugins', VIEW_TYPE), { recursive: true }) fs.cpSync(path.join(FIXTURE_ROOT, 'rows'), path.join(outPath, '_bases_oracle', 'rows'), { recursive: true }) fs.copyFileSync(CASES_PATH, path.join(outPath, '_bases_oracle', 'source-cases.json')) const fixtureFiles = listFiles(outPath, path.join('_bases_oracle', 'rows')) .map(relative => relative.split(path.sep).join('/')) .sort()
const runCases = [] for (const [index, testCase] of cases.cases.entries()) { const input = cases.__canonicalInputs[index] const relative = path.join('_bases_oracle', 'cases', input.relative) fs.mkdirSync(path.dirname(path.join(outPath, relative)), { recursive: true }) fs.copyFileSync(input.absolute, path.join(outPath, relative)) runCases.push({ id: testCase.id, path: relative.replaceAll(path.sep, '/'), probes: testCase.probes, viewName: testCase.capture.viewName, fixtureFiles, preserveRowOrder: testCase.capture.preserveRowOrder ?? false, }) } fs.copyFileSync(pluginBuild, path.join(outPath, '.obsidian', 'plugins', VIEW_TYPE, 'main.js')) fs.copyFileSync(path.join(PLUGIN_ROOT, 'manifest.json'), path.join(outPath, '.obsidian', 'plugins', VIEW_TYPE, 'manifest.json')) fs.writeFileSync(path.join(outPath, '.obsidian', 'community-plugins.json'), `${JSON.stringify([VIEW_TYPE], null, 2)}\n`) fs.writeFileSync(path.join(outPath, '.obsidian', 'core-plugins.json'), `${JSON.stringify(['bases'], null, 2)}\n`)
const artifactPaths = listFiles(outPath) .map(relative => relative.split(path.sep).join('/')) .filter(relative => relative !== '_bases_oracle/run.json' && relative !== '_bases_oracle/raw-results.json' && relative !== '_bases_oracle/run-state.json') .sort() const artifacts = artifactPaths.map(relative => ({ path: relative, sha256: sha256(fs.readFileSync(path.join(outPath, ...relative.split('/')))), })) const pluginRelative = `.obsidian/plugins/${VIEW_TYPE}/main.js` const pluginSha256 = artifacts.find(artifact => artifact.path === pluginRelative)?.sha256 if (!pluginSha256) throw new OracleValidationError('Prepared plugin bundle was not included in artifact provenance') const run = { schemaVersion: cases.schemaVersion, normalizationVersion: cases.normalizationVersion, target: cases.target, fixtureVaultSha256: sha256(artifacts.map(artifact => `${artifact.path}\0${artifact.sha256}\0`).join('')), runId: crypto.randomUUID(), pluginSha256, pluginPromotable: !overridePluginBuild, apiSnapshotSha256: sha256(fs.readFileSync(API_SNAPSHOT_PATH)), harnessSourceSha256: harnessSourceSha256(), locale: cases.locale, timezone: cases.timezone, seed: cases.seed, cases: runCases, artifacts, } fs.writeFileSync(path.join(outPath, '_bases_oracle', 'run.json'), canonicalJson(run)) return { outPath, run, rawResultsPath: path.join(outPath, '_bases_oracle', 'raw-results.json') }}
function loadAndValidateRaw(rawPath, runPath) { const cases = validateCases(readJson(CASES_PATH, 'cases manifest')) const relations = validateRelations(readJson(RELATIONS_PATH, 'relations manifest'), cases) validateInventory(readJson(INVENTORY_PATH, 'coverage inventory'), cases) validateOfficialSyntaxInventory(readJson(OFFICIAL_SYNTAX_INVENTORY_PATH, 'official syntax inventory'), cases) const stageRoot = path.dirname(path.dirname(runPath)) const run = validatePreparedRun(readJson(runPath, 'prepared run manifest'), cases, stageRoot) const runStatePath = path.join(path.dirname(runPath), 'run-state.json') const runState = object(readJson(runStatePath, 'run completion state'), 'run completion state') if (runState.status !== 'complete' || runState.runId !== run.runId) { throw new OracleValidationError('raw results are not bound to a completed current run') } const raw = validateRawResults(readJson(rawPath, 'raw oracle results'), cases, run, relations) return { cases, raw, run, relations }}
function corpusFrom(cases, raw, run, relations) { return { schemaVersion: 1, normalizationVersion: 1, environment: raw.environment, provenance: { target: run.target, artifacts: run.artifacts, }, relations: relations.relations, cases: cases.cases.map((testCase, index) => ({ source: { id: testCase.id, description: testCase.description, coverage: testCase.coverage, capture: testCase.capture, inputPath: testCase.inputPath, configText: cases.__canonicalInputs[index].bytes.toString('utf8'), configSha256: sha256(cases.__canonicalInputs[index].bytes), probes: testCase.probes, generator: testCase.generator ?? null, shrunk: testCase.shrunk ?? null, }, result: raw.cases[index], })), }}
function validateCorpus(value, cases, relations, freshPluginSha256) { const root = object(value, 'committed corpus') exactKeys(root, ['schemaVersion', 'normalizationVersion', 'environment', 'provenance', 'relations', 'cases'], 'committed corpus') if (root.schemaVersion !== 1 || root.normalizationVersion !== 1 || !Array.isArray(root.cases)) { throw new OracleValidationError('committed corpus has invalid versions or cases') } const ids = root.cases.map((item, index) => nonEmptyString(object(object(item, `corpus case ${index}`).source, `corpus case ${index}.source`).id, `corpus case ${index} id`)) if (JSON.stringify(ids) !== JSON.stringify(cases.cases.map(testCase => testCase.id))) { throw new OracleValidationError('committed corpus does not cover the current case manifest in order') } for (const [index, itemValue] of root.cases.entries()) { const item = object(itemValue, `corpus case ${index}`) exactKeys(item, ['source', 'result'], `corpus case ${index}`) const expectedSource = { id: cases.cases[index].id, description: cases.cases[index].description, coverage: cases.cases[index].coverage, capture: cases.cases[index].capture, inputPath: cases.cases[index].inputPath, configText: cases.__canonicalInputs[index].bytes.toString('utf8'), configSha256: sha256(cases.__canonicalInputs[index].bytes), probes: cases.cases[index].probes, generator: cases.cases[index].generator ?? null, shrunk: cases.cases[index].shrunk ?? null, } if (canonicalJson(item.source) !== canonicalJson(expectedSource)) { throw new OracleValidationError(`committed corpus source '${expectedSource.id}' is stale`) } for (const forbidden of ['base', 'original', 'config', 'filters', 'formulas', 'views', 'expected']) { if (forbidden in item.source) throw new OracleValidationError(`committed corpus source '${expectedSource.id}' contains forbidden field '${forbidden}'`) } const sourceBytes = cases.__canonicalInputs[index].bytes if (item.source.configSha256 !== sha256(sourceBytes) || item.source.configText !== sourceBytes.toString('utf8')) { throw new OracleValidationError(`committed corpus source '${expectedSource.id}' does not contain the exact canonical YAML bytes`) } } if (canonicalJson(root.relations) !== canonicalJson(relations.relations)) { throw new OracleValidationError('committed corpus relations are stale') } const environment = object(root.environment, 'committed corpus.environment') const provenance = object(root.provenance, 'committed corpus.provenance') exactKeys(environment, ['targetObsidianVersion', 'runtimeApiVersion', 'apiCommit', 'fixtureVaultSha256', 'runId', 'pluginSha256', 'apiSnapshotSha256', 'harnessSourceSha256', 'locale', 'timezone', 'seed'], 'committed corpus.environment') exactKeys(provenance, ['target', 'artifacts'], 'committed corpus.provenance') if (canonicalJson(provenance.target) !== canonicalJson(cases.target) || !Array.isArray(provenance.artifacts)) { throw new OracleValidationError('committed corpus target/artifacts are invalid') } if (environment.apiSnapshotSha256 !== sha256(fs.readFileSync(API_SNAPSHOT_PATH))) { throw new OracleValidationError('committed corpus API snapshot hash is stale') } if (environment.harnessSourceSha256 !== harnessSourceSha256()) { throw new OracleValidationError('committed corpus harness source hash is stale') } if (typeof environment.runId !== 'string' || !/^[0-9a-f]{8}-[0-9a-f-]{27}$/i.test(environment.runId)) { throw new OracleValidationError('committed corpus runId is not a UUID') } const artifactPaths = new Set() const artifactHashes = new Map() for (const rawArtifact of provenance.artifacts) { const artifact = object(rawArtifact, 'committed corpus artifact') exactKeys(artifact, ['path', 'sha256'], 'committed corpus artifact') nonEmptyString(artifact.path, 'committed corpus artifact.path') nonEmptyString(artifact.sha256, 'committed corpus artifact.sha256') if (artifactPaths.has(artifact.path)) throw new OracleValidationError('committed corpus has duplicate artifacts') artifactPaths.add(artifact.path) artifactHashes.set(artifact.path, artifact.sha256) } validateCanonicalArtifacts(artifactHashes, cases, 'committed corpus') const aggregate = sha256(provenance.artifacts.map(artifact => `${artifact.path}\0${artifact.sha256}\0`).join('')) if (aggregate !== environment.fixtureVaultSha256) throw new OracleValidationError('committed corpus fixture hash is incoherent') const plugin = provenance.artifacts.find(artifact => artifact.path === `.obsidian/plugins/${VIEW_TYPE}/main.js`) if (!plugin || plugin.sha256 !== environment.pluginSha256) throw new OracleValidationError('committed corpus plugin hash is incoherent') if (environment.pluginSha256 !== freshPluginSha256) { throw new OracleValidationError('committed corpus plugin was not built from the current default plugin source') } const fixtureFiles = provenance.artifacts .map(artifact => artifact.path) .filter(artifactPath => artifactPath.startsWith('_bases_oracle/rows/')) validateRawResults({ schemaVersion: root.schemaVersion, normalizationVersion: root.normalizationVersion, environment, cases: root.cases.map(item => item.result), }, cases, { fixtureVaultSha256: aggregate, fixtureFiles, runId: environment.runId, pluginSha256: environment.pluginSha256, apiSnapshotSha256: environment.apiSnapshotSha256, harnessSourceSha256: environment.harnessSourceSha256, }, relations)}
function option(args, name, fallback) { const index = args.indexOf(name) if (index < 0) return fallback if (args[index + 1] === undefined) throw new OracleValidationError(`${name} requires a path`) return path.resolve(args[index + 1])}
function positional(args) { const values = [] for (let index = 0; index < args.length; index += 1) { if (args[index].startsWith('--')) { if (args[index] !== '--accept') index += 1 continue } values.push(args[index]) } return values}
function main() { const [command, ...args] = process.argv.slice(2) if (!command || !['prepare', 'check', 'validate', 'promote'].includes(command)) { fail('Usage: node scripts/bases-oracle.mjs <prepare|check|validate|promote> [options]', true) return } try { if (command === 'prepare') { const result = prepare(option(args, '--out', DEFAULT_STAGE)) console.log(canonicalJson({ status: 'prepared', caseCount: result.run.cases.length, fixtureVaultSha256: result.run.fixtureVaultSha256, vaultPath: result.outPath, rawResultsPath: result.rawResultsPath, next: 'Open this vault in Obsidian 1.11.4, enable Reams Bases Oracle, and run “Reams Bases Oracle: Run Bases oracle”.', }).trimEnd()) return } if (command === 'check') { const cases = validateCases(readJson(CASES_PATH, 'cases manifest')) const relations = validateRelations(readJson(RELATIONS_PATH, 'relations manifest'), cases) validateInventory(readJson(INVENTORY_PATH, 'coverage inventory'), cases) const officialInventory = validateOfficialSyntaxInventory(readJson(OFFICIAL_SYNTAX_INVENTORY_PATH, 'official syntax inventory'), cases) const manifest = object(readJson(path.join(PLUGIN_ROOT, 'manifest.json'), 'plugin manifest'), 'plugin manifest') if (manifest.id !== VIEW_TYPE || manifest.minAppVersion !== EXPECTED_OBSIDIAN_VERSION) { throw new OracleValidationError('plugin identity/minAppVersion is not pinned') } const vendorReadme = fs.readFileSync(path.join(ROOT, 'vendor', 'README.md'), 'utf8') if (!vendorReadme.includes(EXPECTED_API_COMMIT) || !fs.existsSync(API_SNAPSHOT_PATH)) { throw new OracleValidationError('vendored API snapshot provenance does not match the oracle pin') } const policy = object(readJson(CORPUS_POLICY_PATH, 'corpus policy'), 'corpus policy') if (typeof policy.required !== 'boolean') throw new OracleValidationError('corpus policy.required must be boolean') let corpus = 'missing' if (fs.existsSync(CORPUS_PATH)) { validateCorpus(readJson(CORPUS_PATH), cases, relations, freshDefaultPluginSha256()) corpus = 'valid' } else if (policy.required) throw new OracleValidationError('committed corpus is required but missing') const counts = Object.fromEntries(['supported', 'discovery', 'deferred'].map(status => [status, officialInventory.symbols.filter(symbol => symbol.status === status).length])) console.log(canonicalJson({ status: corpus === 'missing' ? 'bootstrap' : 'valid', caseCount: cases.cases.length, corpus, inventory: counts }).trimEnd()) return } const values = positional(args) const rawPath = path.resolve(values[0] ?? DEFAULT_RAW) const inferredRun = path.join(path.dirname(rawPath), 'run.json') const runPath = option(args, '--run', fs.existsSync(inferredRun) ? inferredRun : DEFAULT_RUN) const validated = loadAndValidateRaw(rawPath, runPath) if (command === 'validate') { console.log(canonicalJson({ status: 'valid', caseCount: validated.raw.cases.length, rawPath }).trimEnd()) return } const corpus = corpusFrom(validated.cases, validated.raw, validated.run, validated.relations) const accept = args.includes('--accept') const bytes = canonicalJson(corpus) let destination = CANDIDATE_PATH if (accept) { if (!fs.existsSync(CANDIDATE_PATH) || fs.readFileSync(CANDIDATE_PATH, 'utf8') !== bytes) { throw new OracleValidationError('review candidate is missing or differs; run promote without --accept first') } const freshPluginSha256 = freshDefaultPluginSha256() if (!validated.run.pluginPromotable || validated.run.pluginSha256 !== freshPluginSha256) { throw new OracleValidationError('prepared run plugin was not built from the current default plugin source') } destination = CORPUS_PATH fs.mkdirSync(path.dirname(destination), { recursive: true }) fs.copyFileSync(CANDIDATE_PATH, destination) fs.writeFileSync(CORPUS_POLICY_PATH, canonicalJson({ required: true })) } else { fs.mkdirSync(path.dirname(destination), { recursive: true }) fs.writeFileSync(destination, bytes) } console.log(canonicalJson({ status: accept ? 'promoted' : 'candidate', caseCount: corpus.cases.length, destination, next: accept ? 'Review and commit the corpus diff.' : `Review the candidate, then rerun with --accept to replace ${path.relative(ROOT, CORPUS_PATH)}.`, }).trimEnd()) } catch (error) { fail(error instanceof Error ? error.message : String(error)) }}
main()