fix(pncc): bind artifacts to one committed tree snapshot

This commit is contained in:
冰朔 2026-09-03 12:33:59 +08:00
commit 9e04b0354c

View file

@ -25,6 +25,7 @@ const JOURNAL_SCHEMA = 'guanghu.pncc-runtime-journal/v1'
const STATUS_SCHEMA = 'guanghu.pncc-runtime-status/v1' const STATUS_SCHEMA = 'guanghu.pncc-runtime-status/v1'
const MAX_ARTIFACT_BYTES = 2 * 1024 * 1024 const MAX_ARTIFACT_BYTES = 2 * 1024 * 1024
const MAX_ARTIFACTS = 256 const MAX_ARTIFACTS = 256
const MAX_TREE_ENTRIES = 4096
function sha256(value) { function sha256(value) {
return createHash('sha256').update(value).digest('hex') return createHash('sha256').update(value).digest('hex')
@ -74,13 +75,28 @@ function exactGitRoot(repository) {
return candidate return candidate
} }
function committedArtifact(repository, head, path) { function committedTree(repository, head) {
const raw = runGit(repository, ['ls-tree', '-r', '-z', head], 'PNCC_GIT_TREE_READ', { encoding: 'buffer' })
if (!Buffer.isBuffer(raw) || raw.length === 0) fail('PNCC_COMMITTED_TREE_EMPTY')
const entries = new Map()
for (const record of raw.toString('utf8').split('\0')) {
if (!record) continue
const tab = record.indexOf('\t')
if (tab < 1) fail('PNCC_COMMITTED_TREE_ENTRY_INVALID')
const header = record.slice(0, tab).split(' ')
const path = record.slice(tab + 1)
if (header.length !== 3 || entries.has(path)) fail('PNCC_COMMITTED_TREE_ENTRY_INVALID')
entries.set(path, { mode: header[0], type: header[1], object: validateFullHead(header[2]) })
if (entries.size > MAX_TREE_ENTRIES) fail('PNCC_COMMITTED_TREE_ENTRY_LIMIT_EXCEEDED')
}
return entries
}
function committedArtifact(repository, path, tree) {
validateRelativePath(path) validateRelativePath(path)
const treeEntry = String(runGit(repository, ['ls-tree', head, '--', path], 'PNCC_GIT_TREE_ENTRY_READ')).trim() const entry = tree.get(path)
const match = treeEntry.match(/^([0-9]{6}) blob ([0-9a-f]{40})\t(.+)$/u) if (!entry || entry.type !== 'blob') fail('PNCC_COMMITTED_PATH_NOT_EXACT_BLOB', path)
if (!match || match[3] !== path) fail('PNCC_COMMITTED_PATH_NOT_EXACT_BLOB', path) const { mode, object } = entry
const mode = match[1]
const object = validateFullHead(match[2].toLowerCase())
const type = String(runGit(repository, ['cat-file', '-t', object], 'PNCC_GIT_OBJECT_TYPE_READ')).trim() const type = String(runGit(repository, ['cat-file', '-t', object], 'PNCC_GIT_OBJECT_TYPE_READ')).trim()
if (type !== 'blob') fail('PNCC_COMMITTED_OBJECT_NOT_BLOB', path) if (type !== 'blob') fail('PNCC_COMMITTED_OBJECT_NOT_BLOB', path)
if (!['100644', '100755'].includes(mode)) fail('PNCC_COMMITTED_OBJECT_NOT_REGULAR_FILE', path) if (!['100644', '100755'].includes(mode)) fail('PNCC_COMMITTED_OBJECT_NOT_REGULAR_FILE', path)
@ -91,8 +107,8 @@ function committedArtifact(repository, head, path) {
return { relativePath: path, gitObjectId: object, sha256: sha256(bytes), byteLength: bytes.length, bytes } return { relativePath: path, gitObjectId: object, sha256: sha256(bytes), byteLength: bytes.length, bytes }
} }
function parseManifest(repository, head) { function parseManifest(repository, tree) {
const evidence = committedArtifact(repository, head, MANIFEST_PATH) const evidence = committedArtifact(repository, MANIFEST_PATH, tree)
let manifest let manifest
try { try {
manifest = JSON.parse(evidence.bytes.toString('utf8')) manifest = JSON.parse(evidence.bytes.toString('utf8'))
@ -123,7 +139,8 @@ export function inspectPersonaRepository(repositoryPath, nodeId) {
const head = validateFullHead(String(runGit(repository, ['rev-parse', 'HEAD'], 'PNCC_GIT_HEAD_READ')).trim().toLowerCase()) const head = validateFullHead(String(runGit(repository, ['rev-parse', 'HEAD'], 'PNCC_GIT_HEAD_READ')).trim().toLowerCase())
const dirty = String(runGit(repository, ['status', '--porcelain', '--untracked-files=all'], 'PNCC_GIT_STATUS_READ')).trim() const dirty = String(runGit(repository, ['status', '--porcelain', '--untracked-files=all'], 'PNCC_GIT_STATUS_READ')).trim()
if (dirty) fail('PNCC_REPOSITORY_DIRTY') if (dirty) fail('PNCC_REPOSITORY_DIRTY')
const { manifest, evidence: manifestEvidence } = parseManifest(repository, head) const tree = committedTree(repository, head)
const { manifest, evidence: manifestEvidence } = parseManifest(repository, tree)
if (manifest.primaryNode !== nodeId) fail('PNCC_PRIMARY_NODE_MISMATCH') if (manifest.primaryNode !== nodeId) fail('PNCC_PRIMARY_NODE_MISMATCH')
const paths = new Set([manifest.brainEntry, manifest.currentCheckpoint, manifest.cognitiveGravity.sourcePath]) const paths = new Set([manifest.brainEntry, manifest.currentCheckpoint, manifest.cognitiveGravity.sourcePath])
@ -135,7 +152,7 @@ export function inspectPersonaRepository(repositoryPath, nodeId) {
} }
if (paths.size > MAX_ARTIFACTS) fail('PNCC_DECLARED_ARTIFACT_LIMIT_EXCEEDED') if (paths.size > MAX_ARTIFACTS) fail('PNCC_DECLARED_ARTIFACT_LIMIT_EXCEEDED')
const artifacts = [...paths].sort().map((path) => { const artifacts = [...paths].sort().map((path) => {
const artifact = committedArtifact(repository, head, path) const artifact = committedArtifact(repository, path, tree)
return { relativePath: artifact.relativePath, gitObjectId: artifact.gitObjectId, sha256: artifact.sha256, byteLength: artifact.byteLength } return { relativePath: artifact.relativePath, gitObjectId: artifact.gitObjectId, sha256: artifact.sha256, byteLength: artifact.byteLength }
}) })
return { return {