dsh-harbor-evolution 0.8.3 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,160 @@
1
+ import { createHash } from 'node:crypto'
2
+ import { lstat, readFile } from 'node:fs/promises'
3
+ import path from 'node:path'
4
+
5
+ export const CANDIDATE_RUNTIME_NAME = 'candidate-runtime.json'
6
+ const DESCRIPTOR_KEYS = ['schema_version', 'transport', 'entrypoint', 'config_path', 'agent_entry_id', 'node_version']
7
+ const RESERVED_PATHS = new Set(['node_modules', '.harbor-runtime', '.git'])
8
+ const EXACT_VERSION = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/
9
+ const DEPENDENCY_FIELDS = ['dependencies', 'optionalDependencies', 'devDependencies', 'peerDependencies']
10
+
11
+ const object = value => value !== null && typeof value === 'object' && !Array.isArray(value)
12
+ const sha256 = bytes => `sha256:${createHash('sha256').update(bytes).digest('hex')}`
13
+
14
+ function safeRelative(value, label) {
15
+ if (typeof value !== 'string' || !value || value.length > 1024 || value.trim() !== value || /[\\:\x00-\x1f\x7f]/.test(value) || path.posix.isAbsolute(value)) {
16
+ throw new Error(`${label} must be a safe Candidate-relative path`)
17
+ }
18
+ const parts = value.split('/')
19
+ if (parts.some(part => !part || part === '.' || part === '..' || RESERVED_PATHS.has(part))) {
20
+ throw new Error(`${label} must not traverse or use reserved Candidate paths`)
21
+ }
22
+ return parts
23
+ }
24
+
25
+ async function sourceFile(root, relative, { optional = false } = {}) {
26
+ const parts = safeRelative(relative, relative)
27
+ let absolute = root
28
+ for (let index = 0; index < parts.length; index++) {
29
+ absolute = path.join(absolute, parts[index])
30
+ let info
31
+ try {
32
+ info = await lstat(absolute)
33
+ } catch (error) {
34
+ if (optional && index === parts.length - 1 && error.code === 'ENOENT') return undefined
35
+ if (error.code === 'ENOENT') throw new Error(`Candidate runtime requires a regular source file: ${relative}`)
36
+ throw error
37
+ }
38
+ if (info.isSymbolicLink() || (index === parts.length - 1 ? !info.isFile() : !info.isDirectory())) {
39
+ throw new Error(`Candidate runtime requires a regular source file without symlinks: ${relative}`)
40
+ }
41
+ }
42
+ return readFile(absolute)
43
+ }
44
+
45
+ function parseObject(bytes, label) {
46
+ if (bytes.length > 4 * 1024 * 1024) throw new Error(`${label} exceeds the runtime metadata size limit`)
47
+ let value
48
+ try {
49
+ const source = bytes.toString('utf8')
50
+ if (!Buffer.from(source, 'utf8').equals(bytes)) throw new Error('Invalid UTF-8')
51
+ value = JSON.parse(source)
52
+ } catch {
53
+ throw new Error(`${label} is not valid JSON`)
54
+ }
55
+ if (!object(value)) throw new Error(`${label} must be an object`)
56
+ return value
57
+ }
58
+
59
+ function dependencies(value, field, label) {
60
+ const entries = Object.hasOwn(value, field) ? value[field] : {}
61
+ if (!object(entries)) throw new Error(`${label} ${field} must be an object`)
62
+ for (const [name, version] of Object.entries(entries)) {
63
+ if (typeof version !== 'string' || !EXACT_VERSION.test(version)) {
64
+ throw new Error(`${label} ${field}.${name} must use an exact semver, not a tag, range, alias, or local dependency`)
65
+ }
66
+ }
67
+ return Object.entries(entries).sort(([left], [right]) => left.localeCompare(right))
68
+ }
69
+
70
+ function validateLockfile(packageJson, lockfile) {
71
+ if (lockfile.lockfileVersion !== 3 || !object(lockfile.packages) || !object(lockfile.packages[''])) {
72
+ throw new Error('Candidate runtime requires package-lock.json v3 with packages[\'\'] root metadata')
73
+ }
74
+ const root = lockfile.packages['']
75
+ for (const field of ['name', 'version']) {
76
+ if (typeof packageJson[field] !== 'string' || !packageJson[field] || root[field] !== packageJson[field]) {
77
+ throw new Error(`package-lock.json root ${field} must match package.json ${field}`)
78
+ }
79
+ }
80
+ for (const field of DEPENDENCY_FIELDS) {
81
+ const expected = dependencies(packageJson, field, 'package.json')
82
+ const actual = dependencies(root, field, 'package-lock.json root')
83
+ if (JSON.stringify(expected) !== JSON.stringify(actual)) {
84
+ throw new Error(`package-lock.json root ${field} must match package.json ${field}`)
85
+ }
86
+ for (const [name, version] of expected) {
87
+ const target = lockfile.packages[`node_modules/${name}`]
88
+ if (!object(target) || target.version !== version) {
89
+ throw new Error(`package-lock.json root ${field} dependency must resolve to its exact locked version`)
90
+ }
91
+ }
92
+ }
93
+ for (const [location, entry] of Object.entries(lockfile.packages)) {
94
+ if (!location) continue
95
+ if (!location.startsWith('node_modules/') || /[\\:\x00-\x1f\x7f]/.test(location) || location.split('/').some(part => !part || part === '.' || part === '..' || part === '.git' || part === '.harbor-runtime')) {
96
+ throw new Error(`package-lock.json contains an unsupported package location: ${location}`)
97
+ }
98
+ if (!object(entry) || entry.link || entry.inBundle || typeof entry.version !== 'string' || !EXACT_VERSION.test(entry.version)) {
99
+ throw new Error(`package-lock.json ${location} requires an exact registry version without links or bundled dependencies`)
100
+ }
101
+ let resolved
102
+ try {
103
+ resolved = new URL(entry.resolved)
104
+ } catch {
105
+ throw new Error(`package-lock.json ${location} requires an HTTPS resolved package URL`)
106
+ }
107
+ if (typeof entry.resolved !== 'string' || !entry.resolved.startsWith('https://') || /[\x00-\x20\x7f\\]/.test(entry.resolved) || resolved.protocol !== 'https:' || !resolved.hostname || resolved.username || resolved.password || resolved.search || resolved.hash || entry.resolved.includes('?') || entry.resolved.includes('#')) {
108
+ throw new Error(`package-lock.json ${location} requires an HTTPS resolved package URL without credentials, query, or fragment`)
109
+ }
110
+ const integrity = entry.integrity
111
+ if (typeof integrity !== 'string' || !/^sha512-[A-Za-z0-9+/]{86}==$/.test(integrity) || Buffer.from(integrity.slice(7), 'base64').toString('base64') !== integrity.slice(7)) {
112
+ throw new Error(`package-lock.json ${location} requires a valid sha512 integrity digest`)
113
+ }
114
+ }
115
+ }
116
+
117
+ /** Resolve only Candidate-owned, source-digested runtime metadata; never select a registry default. */
118
+ export async function loadCandidateRuntime(candidateDir, { required = false } = {}) {
119
+ const root = path.resolve(candidateDir)
120
+ const bytes = await sourceFile(root, CANDIDATE_RUNTIME_NAME, { optional: true })
121
+ if (bytes === undefined) {
122
+ if (required) throw new Error(`Candidate runtime is unbound; migrate this Candidate by adding ${CANDIDATE_RUNTIME_NAME} and a locked ACP entrypoint, then create a new snapshot`)
123
+ return { kind: 'deepseek-harness', policy: 'unbound', transport: 'acp' }
124
+ }
125
+ const descriptor = parseObject(bytes, CANDIDATE_RUNTIME_NAME)
126
+ if (Object.keys(descriptor).some(key => !DESCRIPTOR_KEYS.includes(key)) || DESCRIPTOR_KEYS.some(key => !Object.hasOwn(descriptor, key))) {
127
+ throw new Error(`${CANDIDATE_RUNTIME_NAME} requires exactly: ${DESCRIPTOR_KEYS.join(', ')}`)
128
+ }
129
+ if (descriptor.schema_version !== 1 || descriptor.transport !== 'acp') {
130
+ throw new Error(`${CANDIDATE_RUNTIME_NAME} requires schema_version=1 and transport=acp`)
131
+ }
132
+ safeRelative(descriptor.entrypoint, 'entrypoint')
133
+ if (!/\.(?:js|mjs|cjs)$/.test(descriptor.entrypoint)) throw new Error('Candidate runtime entrypoint must be a .js, .mjs, or .cjs source file')
134
+ safeRelative(descriptor.config_path, 'config_path')
135
+ if (typeof descriptor.agent_entry_id !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(descriptor.agent_entry_id)) {
136
+ throw new Error('Candidate runtime agent_entry_id must be a non-empty safe identifier')
137
+ }
138
+ if (typeof descriptor.node_version !== 'string' || !/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(descriptor.node_version) || Number(descriptor.node_version.split('.')[0]) < 22) {
139
+ throw new Error('Candidate runtime node_version must be an exact x.y.z release with major >=22')
140
+ }
141
+ const entrypointBytes = await sourceFile(root, descriptor.entrypoint)
142
+ await sourceFile(root, descriptor.config_path)
143
+ const packageBytes = await sourceFile(root, 'package.json')
144
+ const lockfileBytes = await sourceFile(root, 'package-lock.json')
145
+ validateLockfile(parseObject(packageBytes, 'package.json'), parseObject(lockfileBytes, 'package-lock.json'))
146
+ return {
147
+ kind: 'deepseek-harness',
148
+ policy: 'candidate-locked',
149
+ transport: 'acp',
150
+ descriptor: CANDIDATE_RUNTIME_NAME,
151
+ entrypoint: descriptor.entrypoint,
152
+ config_path: descriptor.config_path,
153
+ agent_entry_id: descriptor.agent_entry_id,
154
+ node_version: descriptor.node_version,
155
+ lockfile: 'package-lock.json',
156
+ descriptor_digest: sha256(bytes),
157
+ entrypoint_digest: sha256(entrypointBytes),
158
+ lockfile_digest: sha256(lockfileBytes),
159
+ }
160
+ }
package/lib/candidate.js CHANGED
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto'
2
2
  import { mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises'
3
3
  import path from 'node:path'
4
4
 
5
- import { CANDIDATE_ACP_PACKAGE, DSH_RUNTIME_VERSION, RUNTIME_POLICY } from './runtime-identity.js'
5
+ import { loadCandidateRuntime } from './candidate-runtime.js'
6
6
 
7
7
  export const MANIFEST_NAME = 'candidate-manifest.json'
8
8
  export const MODEL_BINDING_NAME = 'model-binding.json'
@@ -11,6 +11,7 @@ const EXCLUDED_DIRS = new Set(['.git', 'node_modules', '__pycache__', '.harbor-r
11
11
  const EXCLUDED_FILES = new Set([MANIFEST_NAME, '.DS_Store'])
12
12
  const LOCKFILES = ['package-lock.json', 'pnpm-lock.yaml', 'yarn.lock', 'bun.lock', 'bun.lockb']
13
13
  const CREDENTIAL_FILES = new Set([
14
+ '.npmrc',
14
15
  'credentials.json',
15
16
  'service-account.json',
16
17
  'secrets.json',
@@ -99,14 +100,14 @@ export async function computeCandidate(candidateDir) {
99
100
  return { digest: `sha256:${digest.digest('hex')}`, files }
100
101
  }
101
102
 
102
- async function validateCandidateContract(root) {
103
+ async function validateCandidateContract(root, runtime) {
103
104
  try {
104
105
  await stat(path.join(root, '.harbor-runtime'))
105
106
  throw new Error('Candidate must not contain the reserved .harbor-runtime path')
106
107
  } catch (error) {
107
108
  if (error.code !== 'ENOENT') throw error
108
109
  }
109
- for (const required of ['cordis.yml', 'package.json']) {
110
+ for (const required of [runtime.config_path ?? 'cordis.yml', 'package.json']) {
110
111
  try {
111
112
  if (!(await stat(path.join(root, required))).isFile()) throw new Error()
112
113
  } catch {
@@ -131,7 +132,8 @@ async function validateCandidateContract(root) {
131
132
 
132
133
  export async function snapshotCandidate(candidateDir, options = {}) {
133
134
  const root = path.resolve(candidateDir)
134
- await validateCandidateContract(root)
135
+ const runtime = await loadCandidateRuntime(root)
136
+ await validateCandidateContract(root, runtime)
135
137
  let packageJson
136
138
  try {
137
139
  packageJson = JSON.parse(await readFile(path.join(root, 'package.json'), 'utf8'))
@@ -158,13 +160,7 @@ export async function snapshotCandidate(candidateDir, options = {}) {
158
160
  version: String(version),
159
161
  digest: computed.digest,
160
162
  created_at: new Date().toISOString(),
161
- runtime: {
162
- kind: 'deepseek-harness',
163
- policy: RUNTIME_POLICY,
164
- version: DSH_RUNTIME_VERSION,
165
- package: CANDIDATE_ACP_PACKAGE,
166
- transport: 'acp',
167
- },
163
+ runtime,
168
164
  files: computed.files,
169
165
  metadata,
170
166
  }