2026-08-17 16:22:56 +08:00
|
|
|
|
import { createHash } from 'node:crypto'
|
2026-08-17 16:49:39 +08:00
|
|
|
|
import { readFile, readdir, writeFile } from 'node:fs/promises'
|
2026-08-17 16:22:56 +08:00
|
|
|
|
import path from 'node:path'
|
|
|
|
|
|
import process from 'node:process'
|
|
|
|
|
|
|
|
|
|
|
|
const args = new Map()
|
2026-08-17 16:49:39 +08:00
|
|
|
|
for (let index = 2; index < process.argv.length; index += 2) args.set(process.argv[index], process.argv[index + 1])
|
2026-08-17 16:22:56 +08:00
|
|
|
|
|
2026-08-17 16:49:39 +08:00
|
|
|
|
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)
|
2026-08-17 16:22:56 +08:00
|
|
|
|
const sourceCommit = args.get('--source-commit')
|
|
|
|
|
|
const output = args.get('--output')
|
|
|
|
|
|
const projectionsPath = args.get('--projections')
|
|
|
|
|
|
|
2026-08-17 16:49:39 +08:00
|
|
|
|
if (!repoRoot || !sourceRoot || !sourceCommit || !output || !projectionsPath) {
|
|
|
|
|
|
throw new Error('usage: compile-gls-runtime-registry.mjs --repo-root <REPO-012> --source-commit <sha> --output <manifest.json> --projections <projections.json>')
|
2026-08-17 16:22:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-17 16:49:39 +08:00
|
|
|
|
async function readRequired(relative) {
|
|
|
|
|
|
return readFile(path.join(repoRoot, relative), 'utf8')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-17 16:22:56 +08:00
|
|
|
|
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) {
|
2026-08-17 16:49:39 +08:00
|
|
|
|
return line.replace(/^#+\s*/, '').replaceAll('**', '').replace(new RegExp(`^${id}\\s*[·::-]?\\s*`), '').trim() || id
|
2026-08-17 16:22:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-17 16:49:39 +08:00
|
|
|
|
function sourceDependencies(raw) {
|
|
|
|
|
|
const match = raw.match(/^\s*depends:\s*\[([^\]]*)\]/m)
|
|
|
|
|
|
return [...new Set(match?.[1].match(/GLS-\d{4}/g) || [])].sort()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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]))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-17 16:22:56 +08:00
|
|
|
|
const projectionManifest = JSON.parse(await readFile(projectionsPath, 'utf8'))
|
|
|
|
|
|
if (projectionManifest.schema !== 'hololake.gls-executable-projections/v1' || projectionManifest.source_commit !== sourceCommit) {
|
|
|
|
|
|
throw new Error('projection manifest does not match the selected REPO-012 source commit')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-17 16:49:39 +08:00
|
|
|
|
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)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-17 16:22:56 +08:00
|
|
|
|
const candidates = (await walk(sourceRoot))
|
|
|
|
|
|
.map((relative) => ({ relative, match: path.basename(relative).match(/^(GLS-\d{4}).*\.hdlp$/) }))
|
|
|
|
|
|
.filter((item) => item.match)
|
|
|
|
|
|
.map((item) => ({ id: item.match[1], relative: item.relative }))
|
|
|
|
|
|
const grouped = new Map()
|
|
|
|
|
|
for (const candidate of candidates) {
|
|
|
|
|
|
const group = grouped.get(candidate.id) || []
|
|
|
|
|
|
group.push(candidate.relative)
|
|
|
|
|
|
grouped.set(candidate.id, group)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-17 16:49:39 +08:00
|
|
|
|
const selectedSource = new Map()
|
|
|
|
|
|
for (const [id, sources] of grouped) {
|
2026-08-17 16:22:56 +08:00
|
|
|
|
sources.sort((left, right) => sourceRank(left) - sourceRank(right) || left.localeCompare(right))
|
|
|
|
|
|
const relative = sources[0]
|
2026-08-17 16:49:39 +08:00
|
|
|
|
selectedSource.set(id, {
|
|
|
|
|
|
relative,
|
|
|
|
|
|
raw: await readFile(path.join(sourceRoot, relative), 'utf8'),
|
|
|
|
|
|
alternate_source_count: sources.length - 1,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const 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)
|
|
|
|
|
|
|
|
|
|
|
|
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))
|
2026-08-17 16:22:56 +08:00
|
|
|
|
const projection = projectionManifest.projections[id]
|
2026-08-17 16:49:39 +08:00
|
|
|
|
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,
|
|
|
|
|
|
edge_kind: 'LEGACY_UNTYPED_REFERENCE',
|
|
|
|
|
|
declared_by: `gls/${source.relative}`,
|
|
|
|
|
|
enters_runtime_graph: false,
|
|
|
|
|
|
target_registered: protocolRegistryIds.has(target),
|
|
|
|
|
|
target_numbered_source_available: knownSourceIds.has(target),
|
|
|
|
|
|
})),
|
|
|
|
|
|
...(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),
|
|
|
|
|
|
})),
|
|
|
|
|
|
]
|
|
|
|
|
|
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.length > 0 && !projection) activationBlockers.push('LEGACY_DEPENDENCIES_REQUIRE_TYPED_REVIEW')
|
|
|
|
|
|
if (legacyDependencies.some((target) => !knownSourceIds.has(target)) && !projection) activationBlockers.push('DEPENDENCY_NUMBERED_SOURCE_MISSING')
|
2026-08-17 16:22:56 +08:00
|
|
|
|
protocols.push({
|
|
|
|
|
|
id,
|
|
|
|
|
|
title: cleanHeading(heading || id, id),
|
2026-08-17 16:49:39 +08:00
|
|
|
|
source_status: sourceStatus(source.raw),
|
|
|
|
|
|
source_path: `gls/${source.relative}`,
|
|
|
|
|
|
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),
|
2026-08-17 16:22:56 +08:00
|
|
|
|
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 || [],
|
2026-08-17 16:49:39 +08:00
|
|
|
|
dependency_edges: dependencyEdges,
|
|
|
|
|
|
activation_blockers: activationBlockers,
|
2026-08-17 16:22:56 +08:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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) {
|
2026-08-17 16:49:39 +08:00
|
|
|
|
if (!projectionManifest.projections[dependency]) throw new Error(`${id} depends on a protocol without an executable projection: ${dependency}`)
|
2026-08-17 16:22:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
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
|
2026-08-17 16:49:39 +08:00
|
|
|
|
const dependenciesNotInRegistry = [...legacyDependencyTargets].filter((id) => !protocolRegistryIds.has(id)).sort()
|
|
|
|
|
|
const dependenciesWithoutNumberedSource = [...legacyDependencyTargets].filter((id) => !knownSourceIds.has(id)).sort()
|
|
|
|
|
|
const numberedSourcesNotInProtocolRegistry = [...knownSourceIds].filter((id) => !protocolRegistryIds.has(id)).sort()
|
2026-08-17 16:22:56 +08:00
|
|
|
|
const registry = {
|
2026-08-17 16:49:39 +08:00
|
|
|
|
schema: 'hololake.gls-runtime-manifest/v2',
|
|
|
|
|
|
record_id: 'HLP-GLS-RUNTIME-MANIFEST-002',
|
2026-08-17 16:22:56 +08:00
|
|
|
|
source: {
|
|
|
|
|
|
repository: 'REPO-012',
|
|
|
|
|
|
commit: sourceCommit,
|
|
|
|
|
|
root: 'gls',
|
2026-08-17 16:49:39 +08:00
|
|
|
|
authority_files: authorityFiles,
|
2026-08-17 16:22:56 +08:00
|
|
|
|
},
|
|
|
|
|
|
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',
|
2026-08-17 16:49:39 +08:00
|
|
|
|
legacy_untyped_dependency_behavior: 'AUDIT_ONLY_BLOCKS_NEW_ACTIVATION',
|
|
|
|
|
|
runtime_graph_source: 'EXPLICIT_EXECUTABLE_PROJECTIONS_ONLY',
|
|
|
|
|
|
runtime_dependency_cycles: 'REJECT',
|
2026-08-17 16:22:56 +08:00
|
|
|
|
unknown_protocol: 'FAIL_CLOSED',
|
|
|
|
|
|
},
|
2026-08-17 16:49:39 +08:00
|
|
|
|
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_sources_not_in_protocol_registry: numberedSourcesNotInProtocolRegistry,
|
|
|
|
|
|
legacy_dependency_cycles: legacyDependencyCycles,
|
|
|
|
|
|
discovered_unreconciled_count: protocols.filter((protocol) => protocol.registration.state === 'DISCOVERED_UNRECONCILED').length,
|
|
|
|
|
|
authority_conflict_count: protocols.filter((protocol) => protocol.registration.state === 'REGISTRATION_CONFLICT').length,
|
|
|
|
|
|
},
|
2026-08-17 16:22:56 +08:00
|
|
|
|
protocol_count: protocols.length,
|
|
|
|
|
|
executable_projection_count: executableCount,
|
|
|
|
|
|
inventoried_not_executable_count: protocols.length - executableCount,
|
|
|
|
|
|
protocols,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await writeFile(output, `${JSON.stringify(registry, null, 2)}\n`)
|
2026-08-17 16:49:39 +08:00
|
|
|
|
console.log(`GLS_RUNTIME_MANIFEST_COMPILED protocols=${protocols.length} registered=${protocolRegistryIds.size} executable=${executableCount} legacy_cycles=${legacyDependencyCycles.length} output=${output}`)
|