483 lines
24 KiB
JavaScript
483 lines
24 KiB
JavaScript
import { createHash } from 'node:crypto'
|
||
import { readFile, readdir, writeFile } from 'node:fs/promises'
|
||
import path from 'node:path'
|
||
import process from 'node:process'
|
||
|
||
const args = new Map()
|
||
for (let index = 2; index < process.argv.length; index += 2) args.set(process.argv[index], process.argv[index + 1])
|
||
|
||
const explicitSourceRoot = args.get('--source-root')
|
||
const repoRoot = args.get('--repo-root') || (explicitSourceRoot ? path.dirname(explicitSourceRoot) : null)
|
||
const sourceRoot = explicitSourceRoot || (repoRoot ? path.join(repoRoot, 'gls') : null)
|
||
const sourceCommit = args.get('--source-commit')
|
||
const output = args.get('--output')
|
||
const projectionsPath = args.get('--projections')
|
||
const referencesPath = args.get('--references')
|
||
|
||
if (!repoRoot || !sourceRoot || !sourceCommit || !output || !projectionsPath || !referencesPath) {
|
||
throw new Error('usage: compile-gls-runtime-registry.mjs --repo-root <REPO-012> --source-commit <sha> --output <manifest.json> --projections <projections.json> --references <reference-nodes.json>')
|
||
}
|
||
if (!/^[a-f0-9]{40}$/.test(sourceCommit)) throw new Error('source commit must be a full SHA-1')
|
||
|
||
async function walk(directory, prefix = '') {
|
||
const entries = await readdir(directory, { withFileTypes: true })
|
||
const paths = []
|
||
for (const entry of entries) {
|
||
const relative = prefix ? `${prefix}/${entry.name}` : entry.name
|
||
if (entry.isDirectory()) paths.push(...await walk(path.join(directory, entry.name), relative))
|
||
else paths.push(relative)
|
||
}
|
||
return paths
|
||
}
|
||
|
||
async function readRequired(relative) {
|
||
return readFile(path.join(repoRoot, relative), 'utf8')
|
||
}
|
||
|
||
function sourceRank(relative) {
|
||
if (!relative.includes('/')) return 0
|
||
if (relative.startsWith('standards/')) return 1
|
||
if (relative.includes('/notion-export/')) return 3
|
||
return 2
|
||
}
|
||
|
||
function cleanHeading(line, id) {
|
||
return line.replace(/^#+\s*/, '').replaceAll('**', '').replace(new RegExp(`^${id}\\s*[·::-]?\\s*`), '').trim() || id
|
||
}
|
||
|
||
function sourceStatus(raw) {
|
||
const line = raw.split(/\r?\n/, 100).find((item) => /^\s*(?:>|[-*]\s*)?(?:状态|Status)\s*[::]/i.test(item))
|
||
if (!line) return 'UNSPECIFIED_SOURCE_STATUS'
|
||
return line.replace(/^\s*(?:>|[-*]\s*)?(?:状态|Status)\s*[::]\s*/i, '').replaceAll('`', '').trim()
|
||
}
|
||
|
||
function sourceDependencies(raw) {
|
||
const match = raw.match(/^\s*depends:\s*\[([^\]]*)\]/m)
|
||
return [...new Set(match?.[1].match(/GLS-\d{4}/g) || [])].sort()
|
||
}
|
||
|
||
// REPO-012 的旧 depends 没有声明边语义。Bootstrap Compiler 必须把每条旧边
|
||
// 投影为受限、只读的工程类型;这些边永不自动取得运行效力。真正进入激活图的边
|
||
// 只能来自 gls-executable-projections/v2 的显式 RUNTIME_REQUIRES。
|
||
function typedSourceDependency(sourceId, targetId) {
|
||
const number = Number(targetId.slice(4))
|
||
const recoveryTargets = new Set(['GLS-0304', 'GLS-0308', 'GLS-0827', 'GLS-0836', 'GLS-0843', 'GLS-0845', 'GLS-0846', 'GLS-0847', 'GLS-0848', 'GLS-0849'])
|
||
const bootTargets = new Set(['GLS-0307', 'GLS-0310', 'GLS-0803', 'GLS-0819', 'GLS-0840', 'GLS-0841'])
|
||
const buildTargets = new Set(['GLS-0130', 'GLS-0131', 'GLS-0411', 'GLS-0710', 'GLS-0844'])
|
||
const evidenceTargets = new Set(['GLS-0306', 'GLS-0311', 'GLS-0604'])
|
||
let edgeKind = 'NORMATIVE_REFERENCE'
|
||
if (recoveryTargets.has(targetId) && (sourceId >= 'GLS-0800' || sourceId === 'GLS-0304' || sourceId === 'GLS-0308')) edgeKind = 'RECOVERY_REQUIRES'
|
||
else if (bootTargets.has(targetId) && sourceId >= 'GLS-0800') edgeKind = 'BOOT_REQUIRES'
|
||
else if (buildTargets.has(targetId)) edgeKind = 'BUILD_REQUIRES'
|
||
else if (evidenceTargets.has(targetId)) edgeKind = 'EVIDENCE_ONLY'
|
||
else if ((number >= 300 && number <= 399) || (number >= 400 && number <= 499) || ['GLS-0602', 'GLS-0603', 'GLS-0605'].includes(targetId)) edgeKind = 'SCHEMA_IMPORT'
|
||
return {
|
||
edge_kind: edgeKind,
|
||
classification_basis: 'BOOTSTRAP_COMPILER_V1_FAMILY_AND_TARGET_RULE',
|
||
}
|
||
}
|
||
|
||
function contractKind(family, projectionKind) {
|
||
if (projectionKind) return projectionKind
|
||
const kinds = {
|
||
GLS_ENGINEERING: 'COMPILER_OR_INTERMEDIATE_REPRESENTATION',
|
||
GLP: 'MESSAGE_SCHEMA_OR_SYNC_CONTRACT',
|
||
GLP_CONTROL_PLANE: 'CONTROL_PLANE_STATE_MACHINE',
|
||
GLP_WITNESS: 'APPEND_ONLY_EVIDENCE_LEDGER',
|
||
HLDP: 'LANGUAGE_PROGRAM_PROFILE',
|
||
GLS_IMPLEMENTATION: 'ADAPTER_MODEL_OR_MODULE_CONTRACT',
|
||
AGE: 'EXECUTION_BODY_LIFECYCLE_STATE_MACHINE',
|
||
AGE_OS: 'RESOURCE_SCHEDULER',
|
||
AGE_AUTONOMOUS_RUNTIME: 'TIME_OR_CAPABILITY_EXTENSION_RUNTIME',
|
||
GUANGHU_WORLD: 'WORLD_BOOT_AND_RECOVERY_STATE_MACHINE',
|
||
GUANGHU_NATIVE_ENGINEERING: 'NATIVE_QUALITY_GATE',
|
||
GUANGHU_NATIVE_OS: 'NATIVE_NODE_RUNTIME_CONTRACT',
|
||
GUANGHU_NATIVE_STORAGE: 'NATIVE_STORAGE_CONTRACT',
|
||
GUANGHU_PERSONA_GESTATION: 'GESTATIONAL_INGRESS_OR_REVIEW_PIPELINE',
|
||
GUANGHU_LANGUAGE_WORLD: 'LANGUAGE_WORLD_BOUNDARY_GUARD',
|
||
}
|
||
return kinds[family] || 'UNCLASSIFIED_PROTOCOL_SOURCE'
|
||
}
|
||
|
||
function parseTableAuthorities(raw, authorityKind) {
|
||
const results = []
|
||
for (const line of raw.split(/\r?\n/)) {
|
||
const id = line.match(/^\|\s*(GLS-\d{4})\s*\|/)?.[1]
|
||
if (!id) continue
|
||
results.push({
|
||
id,
|
||
authority_kind: authorityKind,
|
||
declared_source_path: line.match(/`(gls\/[^`]+\.hdlp)`/)?.[1] || null,
|
||
})
|
||
}
|
||
return results
|
||
}
|
||
|
||
function parseSourceManifest(raw) {
|
||
const lines = raw.split(/\r?\n/)
|
||
const results = []
|
||
for (let index = 0; index < lines.length; index += 1) {
|
||
const match = lines[index].match(/^\s*-\s+id:\s*["']?(GLS-\d{4})["']?\s*$/)
|
||
if (!match) continue
|
||
let sourcePath = null
|
||
for (let cursor = index + 1; cursor < lines.length && cursor <= index + 12; cursor += 1) {
|
||
if (/^\s*-\s+id:/.test(lines[cursor])) break
|
||
sourcePath ||= lines[cursor].match(/^\s*source_path:\s*["']?(gls\/[^"']+\.hdlp)["']?\s*$/)?.[1] || null
|
||
}
|
||
results.push({ id: match[1], authority_kind: 'SOURCE_MANIFEST', declared_source_path: sourcePath })
|
||
}
|
||
return results
|
||
}
|
||
|
||
function stronglyConnectedComponents(graph) {
|
||
let nextIndex = 0
|
||
const indices = new Map()
|
||
const lowLinks = new Map()
|
||
const stack = []
|
||
const onStack = new Set()
|
||
const components = []
|
||
function visit(id) {
|
||
indices.set(id, nextIndex)
|
||
lowLinks.set(id, nextIndex)
|
||
nextIndex += 1
|
||
stack.push(id)
|
||
onStack.add(id)
|
||
for (const target of graph.get(id) || []) {
|
||
if (!graph.has(target)) continue
|
||
if (!indices.has(target)) {
|
||
visit(target)
|
||
lowLinks.set(id, Math.min(lowLinks.get(id), lowLinks.get(target)))
|
||
} else if (onStack.has(target)) lowLinks.set(id, Math.min(lowLinks.get(id), indices.get(target)))
|
||
}
|
||
if (lowLinks.get(id) !== indices.get(id)) return
|
||
const component = []
|
||
let current
|
||
do {
|
||
current = stack.pop()
|
||
onStack.delete(current)
|
||
component.push(current)
|
||
} while (current !== id)
|
||
if (component.length > 1 || (graph.get(id) || []).includes(id)) components.push(component.sort())
|
||
}
|
||
for (const id of [...graph.keys()].sort()) if (!indices.has(id)) visit(id)
|
||
return components.sort((left, right) => left[0].localeCompare(right[0]))
|
||
}
|
||
|
||
const projectionManifest = JSON.parse(await readFile(projectionsPath, 'utf8'))
|
||
if (projectionManifest.schema !== 'hololake.gls-executable-projections/v2'
|
||
|| projectionManifest.runtime_graph_rule !== 'ONLY_EXPLICIT_RUNTIME_REQUIRES_EDGES_ENTER_ACTIVATION_GRAPH'
|
||
|| projectionManifest.source_commit !== sourceCommit) {
|
||
throw new Error('projection manifest does not match the selected REPO-012 source commit')
|
||
}
|
||
const referenceManifest = JSON.parse(await readFile(referencesPath, 'utf8'))
|
||
if (referenceManifest.schema !== 'hololake.gls-numbered-reference-nodes/v1'
|
||
|| referenceManifest.record_id !== 'HLP-GLS-NUMBERED-REFERENCE-REGISTRY-001'
|
||
|| referenceManifest.source?.repository !== 'REPO-012'
|
||
|| referenceManifest.source?.commit !== sourceCommit
|
||
|| referenceManifest.policy?.number_is_coordinate_not_authority !== true
|
||
|| referenceManifest.policy?.independent_protocol_source_required_for_execution !== true
|
||
|| referenceManifest.policy?.reference_only_nodes_may_execute !== false
|
||
|| referenceManifest.policy?.unknown_reference !== 'FAIL_CLOSED'
|
||
|| referenceManifest.policy?.unresolved_number_reference_allowed !== false) {
|
||
throw new Error('numbered reference manifest does not match the selected REPO-012 source commit or safety boundary')
|
||
}
|
||
|
||
const protocolRegistryPath = 'gls/GLS-PROTOCOL-REGISTRY.json'
|
||
const glsEntryPath = 'gls/GLS-ENTRY.hdlp'
|
||
const sourceManifestPath = 'gls/SOURCE-MANIFEST.yml'
|
||
const architectureCatalogPath = 'gls/GLS-ARCHITECTURE-CATALOG.hdlp'
|
||
const protocolRegistryRaw = await readRequired(protocolRegistryPath)
|
||
const glsEntryRaw = await readRequired(glsEntryPath)
|
||
const sourceManifestRaw = await readRequired(sourceManifestPath)
|
||
const architectureCatalogRaw = await readRequired(architectureCatalogPath)
|
||
const protocolRegistry = JSON.parse(protocolRegistryRaw)
|
||
|
||
const authorityFiles = [
|
||
[protocolRegistryPath, protocolRegistryRaw, 'PROTOCOL_REGISTRY'],
|
||
[glsEntryPath, glsEntryRaw, 'GLS_ENTRY'],
|
||
[sourceManifestPath, sourceManifestRaw, 'SOURCE_MANIFEST'],
|
||
[architectureCatalogPath, architectureCatalogRaw, 'ARCHITECTURE_CATALOG'],
|
||
].map(([source_path, raw, authority_kind]) => ({
|
||
authority_kind,
|
||
source_path,
|
||
source_sha256: createHash('sha256').update(raw).digest('hex'),
|
||
}))
|
||
|
||
const registryMetadata = new Map()
|
||
const authorityDeclarations = new Map()
|
||
function addAuthority(declaration) {
|
||
const declarations = authorityDeclarations.get(declaration.id) || []
|
||
if (!declarations.some((existing) => existing.authority_kind === declaration.authority_kind)) declarations.push(declaration)
|
||
authorityDeclarations.set(declaration.id, declarations)
|
||
}
|
||
|
||
for (const entry of protocolRegistry.existing_registered || []) {
|
||
registryMetadata.set(entry.id, { ...entry, registry_section: 'existing_registered' })
|
||
addAuthority({ id: entry.id, authority_kind: 'PROTOCOL_REGISTRY', declared_source_path: entry.source || null })
|
||
}
|
||
for (const entry of protocolRegistry.registered_draft_protocols || []) {
|
||
registryMetadata.set(entry.id, { ...entry, registry_section: 'registered_draft_protocols' })
|
||
addAuthority({ id: entry.id, authority_kind: 'PROTOCOL_REGISTRY', declared_source_path: entry.source || null })
|
||
}
|
||
for (const declaration of parseTableAuthorities(glsEntryRaw, 'GLS_ENTRY')) addAuthority(declaration)
|
||
for (const declaration of parseSourceManifest(sourceManifestRaw)) addAuthority(declaration)
|
||
for (const declaration of parseTableAuthorities(architectureCatalogRaw, 'ARCHITECTURE_CATALOG')) addAuthority(declaration)
|
||
|
||
const routingReferences = new Map()
|
||
const routingRoot = path.join(repoRoot, 'routing')
|
||
for (const relative of await walk(routingRoot)) {
|
||
if (!/\.(?:json|hdlp|md|yml|yaml)$/.test(relative)) continue
|
||
const raw = await readFile(path.join(routingRoot, relative), 'utf8')
|
||
for (const id of new Set(raw.match(/GLS-\d{4}/g) || [])) {
|
||
const refs = routingReferences.get(id) || []
|
||
refs.push(`routing/${relative}`)
|
||
routingReferences.set(id, refs)
|
||
}
|
||
}
|
||
|
||
const allSourceFiles = await walk(sourceRoot)
|
||
const candidates = allSourceFiles
|
||
.map((relative) => ({ relative, match: path.basename(relative).match(/^(GLS-\d{4})(?:[^0-9].*)?\.(hdlp|md)$/) }))
|
||
.filter((item) => item.match)
|
||
.map((item) => ({ id: item.match[1], relative: item.relative, source_format: item.match[2] === 'hdlp' ? 'HDLP_PROTOCOL_SOURCE' : 'LEGACY_MARKDOWN_EVIDENCE' }))
|
||
const grouped = new Map()
|
||
for (const candidate of candidates) {
|
||
const group = grouped.get(candidate.id) || []
|
||
group.push(candidate)
|
||
grouped.set(candidate.id, group)
|
||
}
|
||
|
||
const selectedSource = new Map()
|
||
for (const [id, sources] of grouped) {
|
||
const protocolSources = sources.filter((source) => source.source_format === 'HDLP_PROTOCOL_SOURCE')
|
||
if (protocolSources.length === 0) continue
|
||
protocolSources.sort((left, right) => sourceRank(left.relative) - sourceRank(right.relative)
|
||
|| left.relative.localeCompare(right.relative))
|
||
const selected = protocolSources[0]
|
||
const relative = selected.relative
|
||
selectedSource.set(id, {
|
||
relative,
|
||
raw: await readFile(path.join(sourceRoot, relative), 'utf8'),
|
||
source_format: selected.source_format,
|
||
alternate_source_count: protocolSources.length - 1,
|
||
})
|
||
}
|
||
|
||
let knownSourceIds = new Set(selectedSource.keys())
|
||
const protocolRegistryIds = new Set(registryMetadata.keys())
|
||
const draftIds = new Set((protocolRegistry.registered_draft_protocols || []).map((entry) => entry.id))
|
||
const legacyDependencyTargets = new Set()
|
||
const draftGraph = new Map()
|
||
for (const id of draftIds) {
|
||
const dependencies = sourceDependencies(selectedSource.get(id)?.raw || '')
|
||
draftGraph.set(id, dependencies.filter((target) => draftIds.has(target)))
|
||
for (const target of dependencies) legacyDependencyTargets.add(target)
|
||
}
|
||
const legacyDependencyCycles = stronglyConnectedComponents(draftGraph)
|
||
|
||
// 旧 Notion 导出只在现行协议真实引用且独立 HDLP 正本缺席时作为历史证据节点进入。
|
||
// 未被现行图引用的旧页面不得因为文件名像 GLS 编号就自动取得当前注册地位。
|
||
for (const id of [...legacyDependencyTargets].filter((target) => !knownSourceIds.has(target))) {
|
||
if (!authorityDeclarations.has(id)) continue
|
||
const legacySources = (grouped.get(id) || []).filter((source) => source.source_format === 'LEGACY_MARKDOWN_EVIDENCE')
|
||
if (legacySources.length === 0) continue
|
||
legacySources.sort((left, right) => sourceRank(left.relative) - sourceRank(right.relative) || left.relative.localeCompare(right.relative))
|
||
const selected = legacySources[0]
|
||
selectedSource.set(id, {
|
||
relative: selected.relative,
|
||
raw: await readFile(path.join(sourceRoot, selected.relative), 'utf8'),
|
||
source_format: selected.source_format,
|
||
alternate_source_count: legacySources.length - 1,
|
||
})
|
||
}
|
||
knownSourceIds = new Set(selectedSource.keys())
|
||
|
||
const missingSourceTargets = [...legacyDependencyTargets].filter((id) => !knownSourceIds.has(id)).sort()
|
||
const referenceIds = new Set()
|
||
const referenceNodeNumbers = new Set()
|
||
const numberedReferenceNodes = []
|
||
for (const node of referenceManifest.nodes || []) {
|
||
if (!/^GLS-\d{4}$/.test(node.protocol_id)
|
||
|| !/^HLP-GLS-REF-\d{4}$/.test(node.node_number)
|
||
|| !node.title
|
||
|| !['NORMATIVE_REFERENCE', 'SCHEMA_IMPORT', 'EVIDENCE_ONLY'].includes(node.reference_kind)
|
||
|| node.source_state !== 'ROADMAP_REFERENCE_ONLY'
|
||
|| !referenceIds.add(node.protocol_id)
|
||
|| !referenceNodeNumbers.add(node.node_number)) {
|
||
throw new Error(`invalid or duplicated numbered reference node: ${node.protocol_id || 'UNKNOWN'}`)
|
||
}
|
||
const evidencePaths = []
|
||
for (const relative of allSourceFiles) {
|
||
if (!/\.(?:hdlp|md|json|ya?ml)$/.test(relative)) continue
|
||
const raw = await readFile(path.join(sourceRoot, relative), 'utf8')
|
||
if (raw.includes(node.protocol_id)) evidencePaths.push(`gls/${relative}`)
|
||
}
|
||
if (evidencePaths.length === 0) throw new Error(`numbered reference node has no REPO-012 evidence: ${node.protocol_id}`)
|
||
numberedReferenceNodes.push({
|
||
...node,
|
||
execution_state: 'REFERENCE_ONLY_NOT_EXECUTABLE',
|
||
evidence_paths: evidencePaths.sort().slice(0, 12),
|
||
})
|
||
}
|
||
if (missingSourceTargets.length !== referenceIds.size
|
||
|| missingSourceTargets.some((id) => !referenceIds.has(id))
|
||
|| [...referenceIds].some((id) => !missingSourceTargets.includes(id))) {
|
||
throw new Error(`numbered reference registry coverage mismatch: missing=${missingSourceTargets.join(',')} registered=${[...referenceIds].sort().join(',')}`)
|
||
}
|
||
|
||
const protocols = []
|
||
for (const [id, source] of [...selectedSource.entries()].sort(([left], [right]) => left.localeCompare(right))) {
|
||
const heading = source.raw.split(/\r?\n/).find((line) => line.startsWith('#') && line.includes(id))
|
||
const projection = projectionManifest.projections[id]
|
||
const metadata = registryMetadata.get(id) || null
|
||
const authorities = (authorityDeclarations.get(id) || []).sort((left, right) => left.authority_kind.localeCompare(right.authority_kind))
|
||
const declaredPaths = [...new Set(authorities.map((entry) => entry.declared_source_path).filter(Boolean))]
|
||
const authorityConflict = declaredPaths.length > 1
|
||
const registrationState = authorityConflict
|
||
? 'REGISTRATION_CONFLICT'
|
||
: authorities.some((entry) => entry.authority_kind === 'PROTOCOL_REGISTRY')
|
||
? 'REGISTERED_PROTOCOL_REGISTRY'
|
||
: authorities.length > 0
|
||
? 'REGISTERED_OTHER_CANONICAL_INDEX'
|
||
: 'DISCOVERED_UNRECONCILED'
|
||
const legacyDependencies = sourceDependencies(source.raw)
|
||
const dependencyEdges = [
|
||
...legacyDependencies.map((target) => ({
|
||
target,
|
||
...typedSourceDependency(id, target),
|
||
declared_by: `gls/${source.relative}`,
|
||
enters_runtime_graph: false,
|
||
target_registered: protocolRegistryIds.has(target),
|
||
target_numbered_source_available: knownSourceIds.has(target),
|
||
target_number_coordinate_available: knownSourceIds.has(target) || referenceIds.has(target),
|
||
target_resolution: knownSourceIds.has(target) ? 'NUMBERED_PROTOCOL_SOURCE' : referenceIds.has(target) ? 'NUMBERED_REFERENCE_NODE' : 'UNRESOLVED',
|
||
})),
|
||
...(projection?.dependencies || []).map((target) => ({
|
||
target,
|
||
edge_kind: 'RUNTIME_REQUIRES',
|
||
declared_by: 'contracts/gls-executable-projections.json',
|
||
enters_runtime_graph: true,
|
||
target_registered: protocolRegistryIds.has(target),
|
||
target_numbered_source_available: knownSourceIds.has(target),
|
||
target_number_coordinate_available: knownSourceIds.has(target) || referenceIds.has(target),
|
||
target_resolution: knownSourceIds.has(target) ? 'NUMBERED_PROTOCOL_SOURCE' : referenceIds.has(target) ? 'NUMBERED_REFERENCE_NODE' : 'UNRESOLVED',
|
||
})),
|
||
]
|
||
const activationBlockers = []
|
||
if (!projection) activationBlockers.push('NO_EXECUTABLE_ADAPTER')
|
||
if (registrationState === 'DISCOVERED_UNRECONCILED') activationBlockers.push('REGISTRATION_NOT_RECONCILED')
|
||
if (authorityConflict) activationBlockers.push('REGISTRATION_SOURCE_CONFLICT')
|
||
if (legacyDependencies.some((target) => !knownSourceIds.has(target) && !referenceIds.has(target)) && !projection) activationBlockers.push('DEPENDENCY_NUMBER_COORDINATE_MISSING')
|
||
protocols.push({
|
||
id,
|
||
title: cleanHeading(heading || id, id),
|
||
source_status: sourceStatus(source.raw),
|
||
source_path: `gls/${source.relative}`,
|
||
source_format: source.source_format,
|
||
source_sha256: createHash('sha256').update(source.raw).digest('hex'),
|
||
alternate_source_count: source.alternate_source_count,
|
||
registration: {
|
||
state: registrationState,
|
||
authorities,
|
||
declared_source_paths: declaredPaths,
|
||
routing_reference_count: (routingReferences.get(id) || []).length,
|
||
},
|
||
maturity: {
|
||
registry_section: metadata?.registry_section || null,
|
||
registry_status: metadata?.status || null,
|
||
family: metadata?.family || null,
|
||
implementation_evidence: metadata?.implementation || null,
|
||
},
|
||
contract_kind: contractKind(metadata?.family, projection?.projection_kind),
|
||
implementation_stage: projection?.stage || null,
|
||
projection_state: projection ? 'EXECUTABLE_PROJECTION' : 'INVENTORIED_NOT_EXECUTABLE',
|
||
projection_kind: projection?.projection_kind || null,
|
||
adapter: projection?.adapter || null,
|
||
event_kinds: projection?.event_kinds || [],
|
||
dependencies: projection?.dependencies || [],
|
||
dependency_edges: dependencyEdges,
|
||
activation_blockers: activationBlockers,
|
||
})
|
||
}
|
||
|
||
const known = new Set(protocols.map((protocol) => protocol.id))
|
||
for (const [id, projection] of Object.entries(projectionManifest.projections)) {
|
||
if (!known.has(id)) throw new Error(`projection references an unknown protocol: ${id}`)
|
||
for (const dependency of projection.dependencies) {
|
||
if (!projectionManifest.projections[dependency]) throw new Error(`${id} depends on a protocol without an executable projection: ${dependency}`)
|
||
}
|
||
}
|
||
const visiting = new Set()
|
||
const visited = new Set()
|
||
function visit(id) {
|
||
if (visiting.has(id)) throw new Error(`executable protocol dependency cycle at ${id}`)
|
||
if (visited.has(id)) return
|
||
visiting.add(id)
|
||
for (const dependency of projectionManifest.projections[id].dependencies) visit(dependency)
|
||
visiting.delete(id)
|
||
visited.add(id)
|
||
}
|
||
for (const id of Object.keys(projectionManifest.projections)) visit(id)
|
||
|
||
const executableCount = protocols.filter((protocol) => protocol.projection_state === 'EXECUTABLE_PROJECTION').length
|
||
const typedDependencyCounts = {}
|
||
for (const protocol of protocols) {
|
||
for (const edge of protocol.dependency_edges.filter((candidate) => !candidate.enters_runtime_graph)) {
|
||
typedDependencyCounts[edge.edge_kind] = (typedDependencyCounts[edge.edge_kind] || 0) + 1
|
||
}
|
||
}
|
||
const dependenciesNotInRegistry = [...legacyDependencyTargets].filter((id) => !protocolRegistryIds.has(id)).sort()
|
||
const dependenciesWithoutNumberedSource = [...legacyDependencyTargets].filter((id) => !knownSourceIds.has(id)).sort()
|
||
const unresolvedNumberReferences = dependenciesWithoutNumberedSource.filter((id) => !referenceIds.has(id))
|
||
const numberedSourcesNotInProtocolRegistry = [...knownSourceIds].filter((id) => !protocolRegistryIds.has(id)).sort()
|
||
const registry = {
|
||
schema: 'hololake.gls-runtime-manifest/v2',
|
||
record_id: 'HLP-GLS-RUNTIME-MANIFEST-002',
|
||
source: {
|
||
repository: 'REPO-012',
|
||
commit: sourceCommit,
|
||
root: 'gls',
|
||
authority_files: authorityFiles,
|
||
},
|
||
compiler: {
|
||
source_protocol_is_human_and_machine_authority: true,
|
||
raw_protocol_text_executed: false,
|
||
arbitrary_protocol_code_allowed: false,
|
||
executable_projection_requires_explicit_adapter: true,
|
||
unprojected_protocol_behavior: 'INVENTORIED_NOT_EXECUTABLE',
|
||
source_dependency_behavior: 'TYPED_AUDIT_ONLY_NEVER_ACTIVATES',
|
||
runtime_graph_source: 'EXPLICIT_EXECUTABLE_PROJECTIONS_ONLY',
|
||
runtime_dependency_cycles: 'REJECT',
|
||
unknown_protocol: 'FAIL_CLOSED',
|
||
},
|
||
reconciliation: {
|
||
numbered_protocol_count: protocols.length,
|
||
protocol_registry_id_count: protocolRegistryIds.size,
|
||
existing_registered_count: (protocolRegistry.existing_registered || []).length,
|
||
registered_draft_count: (protocolRegistry.registered_draft_protocols || []).length,
|
||
registered_draft_not_started_count: (protocolRegistry.registered_draft_protocols || []).filter((entry) => entry.implementation === 'NOT_STARTED').length,
|
||
legacy_dependency_target_count: legacyDependencyTargets.size,
|
||
dependencies_not_in_protocol_registry: dependenciesNotInRegistry,
|
||
dependencies_without_numbered_source: dependenciesWithoutNumberedSource,
|
||
numbered_reference_node_count: numberedReferenceNodes.length,
|
||
unresolved_number_references: unresolvedNumberReferences,
|
||
unresolved_number_reference_count: unresolvedNumberReferences.length,
|
||
every_dependency_has_number_coordinate: unresolvedNumberReferences.length === 0,
|
||
numbered_sources_not_in_protocol_registry: numberedSourcesNotInProtocolRegistry,
|
||
legacy_dependency_cycles: legacyDependencyCycles,
|
||
source_reference_cycles: legacyDependencyCycles,
|
||
typed_source_dependency_counts: Object.fromEntries(Object.entries(typedDependencyCounts).sort()),
|
||
unclassified_source_dependency_count: 0,
|
||
discovered_unreconciled_count: protocols.filter((protocol) => protocol.registration.state === 'DISCOVERED_UNRECONCILED').length,
|
||
authority_conflict_count: protocols.filter((protocol) => protocol.registration.state === 'REGISTRATION_CONFLICT').length,
|
||
},
|
||
protocol_count: protocols.length,
|
||
executable_projection_count: executableCount,
|
||
inventoried_not_executable_count: protocols.length - executableCount,
|
||
number_coordinate_count: protocols.length + numberedReferenceNodes.length,
|
||
numbered_reference_nodes: numberedReferenceNodes,
|
||
protocols,
|
||
}
|
||
|
||
await writeFile(output, `${JSON.stringify(registry, null, 2)}\n`)
|
||
console.log(`GLS_RUNTIME_MANIFEST_COMPILED protocols=${protocols.length} references=${numberedReferenceNodes.length} unresolved=${unresolvedNumberReferences.length} registered=${protocolRegistryIds.size} executable=${executableCount} legacy_cycles=${legacyDependencyCycles.length} output=${output}`)
|