@holoscript/holosystem 0.1.2 → 0.2.1
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.
- package/README.md +53 -8
- package/bin/holosystem.mjs +122 -2
- package/package.json +6 -6
- package/src/catalog.mjs +587 -0
- package/src/index.mjs +15 -0
package/README.md
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# @holoscript/holosystem
|
|
2
2
|
|
|
3
|
-
Create and inspect a portable HoloSystem consumer configuration
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
Create and inspect a portable HoloSystem consumer configuration, discover a
|
|
4
|
+
multi-rail public consumption catalog, map registry artifacts to canonical
|
|
5
|
+
source repositories, and select bounded next work. The package is for external
|
|
6
|
+
founders, agent operators, and cold consumers who need the same evidence
|
|
7
|
+
contract without a private workspace or machine-specific defaults.
|
|
7
8
|
|
|
8
9
|
```bash
|
|
9
10
|
npm install @holoscript/holosystem
|
|
@@ -11,6 +12,49 @@ npx holosystem create --id acme-founder --workspace acme --json
|
|
|
11
12
|
npx holosystem inspect holosystem.config.json --json
|
|
12
13
|
```
|
|
13
14
|
|
|
15
|
+
## Consumption Catalog
|
|
16
|
+
|
|
17
|
+
The catalog keeps npm, PyPI, GitHub repositories, public services, containers,
|
|
18
|
+
and agent surfaces as separate rails. It never creates a misleading grand total.
|
|
19
|
+
Each rail reports publication, consumer readiness, dogfood evidence when known,
|
|
20
|
+
and gaps independently.
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx holosystem lineage \
|
|
24
|
+
--portfolio portfolio-consumer.json \
|
|
25
|
+
--output source-lineage.json \
|
|
26
|
+
--json
|
|
27
|
+
|
|
28
|
+
npx holosystem catalog \
|
|
29
|
+
--seeds public-surface-seeds.json \
|
|
30
|
+
--portfolio portfolio-consumer.json \
|
|
31
|
+
--manifest package-manifest.json \
|
|
32
|
+
--lineage source-lineage.json \
|
|
33
|
+
--output consumption-catalog.json \
|
|
34
|
+
--json
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`lineage` reads registry metadata and records canonical repository URLs and
|
|
38
|
+
portable package directories. Unknown lineage remains a named gap; local source
|
|
39
|
+
paths are never emitted. `catalog` performs public GitHub, service, container,
|
|
40
|
+
MCP-health, and public-skill discovery, then joins those results with the
|
|
41
|
+
caller's package admission, lineage, proof-batch, and promotion receipts.
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
import {
|
|
45
|
+
buildConsumptionSurfaceCatalog,
|
|
46
|
+
buildSourceLineageReceipt,
|
|
47
|
+
discoverConsumptionSurfaceCatalog,
|
|
48
|
+
discoverSourceLineage,
|
|
49
|
+
selectNextConsumptionWork,
|
|
50
|
+
} from '@holoscript/holosystem';
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The bounded selector excludes artifacts already present in active proof batches,
|
|
54
|
+
returns one highest-priority candidate, and carries authority, validation, lease,
|
|
55
|
+
and spend stop conditions. It selects work; it does not claim a board task,
|
|
56
|
+
publish a package, spend funds, or bypass caller authority.
|
|
57
|
+
|
|
14
58
|
## What It Creates
|
|
15
59
|
|
|
16
60
|
The default configuration pins public contracts for:
|
|
@@ -99,7 +143,8 @@ package check, an npm pack dry run, and the repository public-consumption gate.
|
|
|
99
143
|
|
|
100
144
|
Release lane: `v0-preview`. Supported behavior is deterministic config creation
|
|
101
145
|
and static, secret-safe portability inspection for the documented v1 schema.
|
|
102
|
-
Known limitations: this package does not
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
146
|
+
Known limitations: this package does not install package dependencies, test live
|
|
147
|
+
credentials, bootstrap databases, claim team work, or authorize publishing.
|
|
148
|
+
Public catalog and lineage discovery depend on caller network access and the
|
|
149
|
+
availability of the queried registries and endpoints. Roll back by pinning the
|
|
150
|
+
previous package version and retaining the consumer-owned config and receipts.
|
package/bin/holosystem.mjs
CHANGED
|
@@ -2,21 +2,29 @@
|
|
|
2
2
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
createHoloSystemConfig,
|
|
7
|
+
discoverConsumptionSurfaceCatalog,
|
|
8
|
+
discoverSourceLineage,
|
|
9
|
+
inspectHoloSystemConfig,
|
|
10
|
+
} from '../src/index.mjs';
|
|
6
11
|
|
|
7
12
|
const CLI_RECEIPT_SCHEMA = 'holoscript.holosystem.cli-receipt.v1';
|
|
8
|
-
const HELP = `holosystem -
|
|
13
|
+
const HELP = `holosystem - portable HoloSystem consumer configuration and evidence
|
|
9
14
|
|
|
10
15
|
Usage:
|
|
11
16
|
holosystem create [--id <id>] [--workspace <id>] [--output <file>] [--force] [--json]
|
|
12
17
|
holosystem create --stdout
|
|
13
18
|
holosystem inspect [file|-] [--json]
|
|
19
|
+
holosystem catalog --seeds <file> --portfolio <file> --manifest <file> [--lineage <file>] [--active-batches <file>] [--promotions <file>] [--output <file>] [--json]
|
|
20
|
+
holosystem lineage --portfolio <file> [--concurrency <1-12>] [--output <file>] [--json]
|
|
14
21
|
holosystem --help
|
|
15
22
|
holosystem --version
|
|
16
23
|
|
|
17
24
|
Defaults:
|
|
18
25
|
create writes holosystem.config.json and never overwrites it without --force.
|
|
19
26
|
inspect reads holosystem.config.json. Use - to read JSON from stdin.
|
|
27
|
+
catalog and lineage read caller-owned evidence and never read credentials.
|
|
20
28
|
`;
|
|
21
29
|
|
|
22
30
|
function die(message, { json = false, code = 1 } = {}) {
|
|
@@ -124,6 +132,23 @@ function readStdin() {
|
|
|
124
132
|
return readFileSync(0, 'utf8');
|
|
125
133
|
}
|
|
126
134
|
|
|
135
|
+
function readJsonFile(path, label) {
|
|
136
|
+
if (!path) throw new Error(`--${label} is required.`);
|
|
137
|
+
const absolute = resolve(process.cwd(), path);
|
|
138
|
+
return JSON.parse(readFileSync(absolute, 'utf8').replace(/^\uFEFF/u, ''));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function readOptionalJsonFile(path, fallback) {
|
|
142
|
+
if (!path) return fallback;
|
|
143
|
+
return JSON.parse(readFileSync(resolve(process.cwd(), path), 'utf8').replace(/^\uFEFF/u, ''));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function writeJsonOutput(path, value, { force = false } = {}) {
|
|
147
|
+
const absolute = resolve(process.cwd(), path);
|
|
148
|
+
if (existsSync(absolute) && !force) throw new Error(`${path} already exists; use --force to replace it.`);
|
|
149
|
+
writeFileSync(absolute, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
150
|
+
}
|
|
151
|
+
|
|
127
152
|
function runInspect(args) {
|
|
128
153
|
let parsed;
|
|
129
154
|
try {
|
|
@@ -157,6 +182,97 @@ function runInspect(args) {
|
|
|
157
182
|
if (!report.ready) process.exitCode = 2;
|
|
158
183
|
}
|
|
159
184
|
|
|
185
|
+
async function runCatalog(args) {
|
|
186
|
+
let parsed;
|
|
187
|
+
try {
|
|
188
|
+
parsed = parseArguments(args, {
|
|
189
|
+
seeds: 'value',
|
|
190
|
+
portfolio: 'value',
|
|
191
|
+
manifest: 'value',
|
|
192
|
+
lineage: 'value',
|
|
193
|
+
'active-batches': 'value',
|
|
194
|
+
promotions: 'value',
|
|
195
|
+
output: 'value',
|
|
196
|
+
force: 'boolean',
|
|
197
|
+
json: 'boolean',
|
|
198
|
+
});
|
|
199
|
+
} catch (error) {
|
|
200
|
+
die(error.message, { json: args.includes('--json') });
|
|
201
|
+
}
|
|
202
|
+
const { options, positionals } = parsed;
|
|
203
|
+
if (positionals.length > 0) die('catalog does not accept positional arguments.', { json: options.json });
|
|
204
|
+
let catalog;
|
|
205
|
+
try {
|
|
206
|
+
const seeds = readJsonFile(options.seeds, 'seeds');
|
|
207
|
+
const portfolio = readJsonFile(options.portfolio, 'portfolio');
|
|
208
|
+
const manifest = readJsonFile(options.manifest, 'manifest');
|
|
209
|
+
const lineage = readOptionalJsonFile(options.lineage, null);
|
|
210
|
+
const activeProofBatches = readOptionalJsonFile(options['active-batches'], []);
|
|
211
|
+
const promotionHistory = readOptionalJsonFile(options.promotions, []);
|
|
212
|
+
catalog = await discoverConsumptionSurfaceCatalog({
|
|
213
|
+
seeds,
|
|
214
|
+
portfolio,
|
|
215
|
+
manifest,
|
|
216
|
+
lineage,
|
|
217
|
+
activeProofBatches: Array.isArray(activeProofBatches) ? activeProofBatches : activeProofBatches.batches,
|
|
218
|
+
promotionHistory: Array.isArray(promotionHistory) ? promotionHistory : promotionHistory.promotions,
|
|
219
|
+
evidence: {
|
|
220
|
+
operatingSet: options.manifest,
|
|
221
|
+
packageAdmission: options.portfolio,
|
|
222
|
+
sourceLineage: options.lineage || 'not-supplied',
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
if (options.output) writeJsonOutput(options.output, catalog, { force: options.force });
|
|
226
|
+
} catch (error) {
|
|
227
|
+
die(`Cannot build catalog: ${error.message}`, { json: options.json, code: 2 });
|
|
228
|
+
}
|
|
229
|
+
if (options.json) outputJson(catalog);
|
|
230
|
+
else {
|
|
231
|
+
process.stdout.write(`Catalog: ${catalog.status}\n`);
|
|
232
|
+
for (const [id, rail] of Object.entries(catalog.rails)) {
|
|
233
|
+
process.stdout.write(`${id}: published=${rail.published} ready=${rail.consumerReady} gaps=${rail.gaps}\n`);
|
|
234
|
+
}
|
|
235
|
+
if (options.output) process.stdout.write(`Wrote ${options.output}\n`);
|
|
236
|
+
}
|
|
237
|
+
if (catalog.status !== 'current') process.exitCode = 2;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function runLineage(args) {
|
|
241
|
+
let parsed;
|
|
242
|
+
try {
|
|
243
|
+
parsed = parseArguments(args, {
|
|
244
|
+
portfolio: 'value',
|
|
245
|
+
concurrency: 'value',
|
|
246
|
+
output: 'value',
|
|
247
|
+
force: 'boolean',
|
|
248
|
+
json: 'boolean',
|
|
249
|
+
});
|
|
250
|
+
} catch (error) {
|
|
251
|
+
die(error.message, { json: args.includes('--json') });
|
|
252
|
+
}
|
|
253
|
+
const { options, positionals } = parsed;
|
|
254
|
+
if (positionals.length > 0) die('lineage does not accept positional arguments.', { json: options.json });
|
|
255
|
+
let lineage;
|
|
256
|
+
try {
|
|
257
|
+
const portfolio = readJsonFile(options.portfolio, 'portfolio');
|
|
258
|
+
lineage = await discoverSourceLineage({
|
|
259
|
+
portfolio,
|
|
260
|
+
concurrency: options.concurrency ? Number(options.concurrency) : 6,
|
|
261
|
+
});
|
|
262
|
+
if (options.output) writeJsonOutput(options.output, lineage, { force: options.force });
|
|
263
|
+
} catch (error) {
|
|
264
|
+
die(`Cannot build lineage: ${error.message}`, { json: options.json, code: 2 });
|
|
265
|
+
}
|
|
266
|
+
if (options.json) outputJson(lineage);
|
|
267
|
+
else {
|
|
268
|
+
process.stdout.write(
|
|
269
|
+
`Lineage: ${lineage.status} mapped=${lineage.summary.mapped}/${lineage.summary.total} gaps=${lineage.summary.gaps}\n`
|
|
270
|
+
);
|
|
271
|
+
if (options.output) process.stdout.write(`Wrote ${options.output}\n`);
|
|
272
|
+
}
|
|
273
|
+
if (lineage.status !== 'complete') process.exitCode = 2;
|
|
274
|
+
}
|
|
275
|
+
|
|
160
276
|
const argv = process.argv.slice(2);
|
|
161
277
|
const command = argv[0];
|
|
162
278
|
if (!command || command === '--help' || command === '-h' || command === 'help') {
|
|
@@ -168,6 +284,10 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
|
|
|
168
284
|
runCreate(argv.slice(1));
|
|
169
285
|
} else if (command === 'inspect') {
|
|
170
286
|
runInspect(argv.slice(1));
|
|
287
|
+
} else if (command === 'catalog') {
|
|
288
|
+
await runCatalog(argv.slice(1));
|
|
289
|
+
} else if (command === 'lineage') {
|
|
290
|
+
await runLineage(argv.slice(1));
|
|
171
291
|
} else {
|
|
172
292
|
die(`Unknown command ${command}. Run holosystem --help.`);
|
|
173
293
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holoscript/holosystem",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "Portable
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Portable bootstrap, consumption catalog, lineage, and bounded-work contract for HoloSystem consumers.",
|
|
5
5
|
"releaseLane": "v0-preview",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"holoscript",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"node": ">=18"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
|
-
"build": "node --check src/index.mjs && node --check bin/holosystem.mjs",
|
|
45
|
-
"test": "node --test test
|
|
44
|
+
"build": "node --check src/index.mjs && node --check src/catalog.mjs && node --check bin/holosystem.mjs",
|
|
45
|
+
"test": "node --test test/*.test.mjs",
|
|
46
46
|
"smoke": "node bin/holosystem.mjs --help",
|
|
47
47
|
"check": "pnpm run build && pnpm run test && pnpm run smoke",
|
|
48
48
|
"prepublishOnly": "pnpm run check"
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
"releaseLane": "v0-preview",
|
|
52
52
|
"pypiCompanion": "holoscript-holorepo",
|
|
53
53
|
"publicConsumption": {
|
|
54
|
-
"coreRule": "Cold consumers create and inspect
|
|
54
|
+
"coreRule": "Cold consumers create and inspect configuration, discover unlike public rails separately, map source lineage, and select bounded next work from caller-owned evidence.",
|
|
55
55
|
"localBoundary": "Machine paths, credentials, private repositories, and operator state are caller inputs, never package defaults.",
|
|
56
|
-
"supportBoundary": "Configuration
|
|
56
|
+
"supportBoundary": "Configuration, public discovery, lineage, and bounded selection are supported; installation, credential acquisition, board mutation, service mutation, spending, and publishing remain caller-authorized operations."
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
}
|
package/src/catalog.mjs
ADDED
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
export const HOLOSYSTEM_CATALOG_SCHEMA = 'holoscript.holosystem.consumption-catalog.v1';
|
|
4
|
+
export const HOLOSYSTEM_LINEAGE_SCHEMA = 'holoscript.holosystem.source-lineage.v1';
|
|
5
|
+
export const HOLOSYSTEM_NEXT_WORK_SCHEMA = 'holoscript.holosystem.next-consumption-work.v1';
|
|
6
|
+
export const HOLOSYSTEM_CONSUMER_INPUT_SCHEMA = 'holoscript.holosystem.consumer-input.v1';
|
|
7
|
+
|
|
8
|
+
const STOP_CONDITIONS = [
|
|
9
|
+
'authority-required',
|
|
10
|
+
'validation-failed',
|
|
11
|
+
'lease-expired',
|
|
12
|
+
'spend-limit-reached',
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
function list(value) {
|
|
16
|
+
return Array.isArray(value) ? value : [];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function clone(value) {
|
|
20
|
+
return JSON.parse(JSON.stringify(value));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function hashReceipt(value) {
|
|
24
|
+
return `sha256:${createHash('sha256').update(JSON.stringify(value)).digest('hex')}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function exactPublicVersion(value) {
|
|
28
|
+
return typeof value === 'string'
|
|
29
|
+
&& /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u.test(value)
|
|
30
|
+
&& !/^(?:file|workspace|link|portal|git|https?):/iu.test(value);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function inspectPublicDependencySpecs(dependencies = {}) {
|
|
34
|
+
const packages = Object.entries(dependencies)
|
|
35
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
36
|
+
.map(([name, version]) => ({
|
|
37
|
+
name,
|
|
38
|
+
version,
|
|
39
|
+
exactPublicVersion: exactPublicVersion(version),
|
|
40
|
+
}));
|
|
41
|
+
const issues = packages
|
|
42
|
+
.filter((item) => !item.exactPublicVersion)
|
|
43
|
+
.map((item) => ({
|
|
44
|
+
code: 'dependency-not-exact-public-version',
|
|
45
|
+
name: item.name,
|
|
46
|
+
version: item.version,
|
|
47
|
+
}));
|
|
48
|
+
return {
|
|
49
|
+
schema: HOLOSYSTEM_CONSUMER_INPUT_SCHEMA,
|
|
50
|
+
ready: issues.length === 0,
|
|
51
|
+
packages,
|
|
52
|
+
issues,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function hashConsumerInput({ dependencies = {}, manifest = {}, requirements = [] }) {
|
|
57
|
+
const dependencyInspection = inspectPublicDependencySpecs(dependencies);
|
|
58
|
+
const normalized = {
|
|
59
|
+
dependencies: dependencyInspection.packages.map(({ name, version }) => ({ name, version })),
|
|
60
|
+
npm: list(manifest?.npm)
|
|
61
|
+
.map((item) => ({
|
|
62
|
+
name: item?.name || null,
|
|
63
|
+
version: item?.version || null,
|
|
64
|
+
integrity: item?.integrity || null,
|
|
65
|
+
probeKind: item?.probe?.kind || 'import',
|
|
66
|
+
}))
|
|
67
|
+
.sort((left, right) => String(left.name).localeCompare(String(right.name))),
|
|
68
|
+
pypi: list(manifest?.pypi)
|
|
69
|
+
.map((item) => ({
|
|
70
|
+
name: item?.name || null,
|
|
71
|
+
version: item?.version || null,
|
|
72
|
+
hashes: list(item?.hashes).slice().sort(),
|
|
73
|
+
}))
|
|
74
|
+
.sort((left, right) => String(left.name).localeCompare(String(right.name))),
|
|
75
|
+
requirements: list(requirements).map(String).sort(),
|
|
76
|
+
};
|
|
77
|
+
return {
|
|
78
|
+
schema: HOLOSYSTEM_CONSUMER_INPUT_SCHEMA,
|
|
79
|
+
ready: dependencyInspection.ready,
|
|
80
|
+
hash: hashReceipt(normalized),
|
|
81
|
+
normalized,
|
|
82
|
+
issues: dependencyInspection.issues,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function artifactKey(ecosystem, name) {
|
|
87
|
+
return `${String(ecosystem || '').toLowerCase()}:${String(name || '').toLowerCase()}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function portableDirectory(value) {
|
|
91
|
+
if (typeof value !== 'string' || !value.trim()) return null;
|
|
92
|
+
const normalized = value.trim().replaceAll('\\', '/').replace(/^\.\//u, '');
|
|
93
|
+
if (/^(?:[A-Za-z]:|\/|~\/)/u.test(normalized) || /(?:^|\/)\.\.(?:\/|$)/u.test(normalized)) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
return normalized.replace(/\/$/u, '') || null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function normalizeRepositoryUrl(value) {
|
|
100
|
+
const raw = typeof value === 'string' ? value : value?.url;
|
|
101
|
+
if (typeof raw !== 'string' || !raw.trim()) return null;
|
|
102
|
+
let normalized = raw.trim();
|
|
103
|
+
normalized = normalized.replace(/^git\+/u, '');
|
|
104
|
+
normalized = normalized.replace(/^git:\/\/github\.com\//u, 'https://github.com/');
|
|
105
|
+
normalized = normalized.replace(/^git@github\.com:/u, 'https://github.com/');
|
|
106
|
+
normalized = normalized.replace(/\.git\/?$/u, '');
|
|
107
|
+
normalized = normalized.replace(/\/$/u, '');
|
|
108
|
+
return /^https?:\/\//u.test(normalized) ? normalized : null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function packageRail(portfolio, manifest, ecosystem, evidence) {
|
|
112
|
+
const records = list(portfolio?.packages).filter((row) => row.ecosystem === ecosystem);
|
|
113
|
+
const dogfoodNames = new Set(list(manifest?.[ecosystem]).map((row) => row.name));
|
|
114
|
+
return {
|
|
115
|
+
published: records.length,
|
|
116
|
+
consumerReady: records.filter((row) => row.classification === 'passing').length,
|
|
117
|
+
dogfooded: records.filter((row) => dogfoodNames.has(row.name)).length,
|
|
118
|
+
gaps: records.filter((row) => row.classification !== 'passing').length,
|
|
119
|
+
evidence,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function countRail(items) {
|
|
124
|
+
return {
|
|
125
|
+
published: items.filter((item) => item.published).length,
|
|
126
|
+
consumerReady: items.filter((item) => item.contractReady).length,
|
|
127
|
+
gaps: items.filter((item) => item.published && !item.contractReady).length,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function packageFailureCategories(portfolio) {
|
|
132
|
+
const gaps = list(portfolio?.packages).filter((row) => row.classification !== 'passing');
|
|
133
|
+
const issueCount = (prefix) => gaps.filter((row) => list(row.issues).some((issue) => issue.startsWith(prefix))).length;
|
|
134
|
+
return {
|
|
135
|
+
total: gaps.length,
|
|
136
|
+
missing: gaps.filter((row) => row.classification === 'missing').length,
|
|
137
|
+
stale: gaps.filter((row) => row.classification === 'stale').length,
|
|
138
|
+
failed: gaps.filter((row) => row.classification === 'failed').length,
|
|
139
|
+
exactVersion: issueCount('exactVersion-'),
|
|
140
|
+
integrity: issueCount('integrity-'),
|
|
141
|
+
import: issueCount('import-'),
|
|
142
|
+
readback: issueCount('readback-'),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function projectProofBatches(value) {
|
|
147
|
+
return list(value).slice(-25).map((batch) => ({
|
|
148
|
+
id: typeof batch?.id === 'string' ? batch.id : null,
|
|
149
|
+
status: typeof batch?.status === 'string' ? batch.status : 'unknown',
|
|
150
|
+
generatedAt: typeof batch?.generatedAt === 'string' ? batch.generatedAt : null,
|
|
151
|
+
packages: list(batch?.packages).slice(0, 100).map((item) => (
|
|
152
|
+
typeof item === 'string'
|
|
153
|
+
? item
|
|
154
|
+
: { ecosystem: item?.ecosystem || null, name: item?.name || null }
|
|
155
|
+
)),
|
|
156
|
+
summary: {
|
|
157
|
+
total: Number(batch?.summary?.total ?? batch?.packageCount) || 0,
|
|
158
|
+
passing: Number(batch?.summary?.passing ?? batch?.passing) || 0,
|
|
159
|
+
failed: Number(batch?.summary?.failed ?? batch?.failed) || 0,
|
|
160
|
+
},
|
|
161
|
+
receiptHash: typeof batch?.receiptHash === 'string' ? batch.receiptHash : null,
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function projectPromotionHistory(value) {
|
|
166
|
+
return list(value).slice(-25).map((attempt) => ({
|
|
167
|
+
id: typeof attempt?.id === 'string' ? attempt.id : null,
|
|
168
|
+
status: typeof attempt?.status === 'string' ? attempt.status : 'unknown',
|
|
169
|
+
eligible: attempt?.eligible === true,
|
|
170
|
+
generatedAt: typeof attempt?.generatedAt === 'string' ? attempt.generatedAt : null,
|
|
171
|
+
receiptHash: typeof attempt?.receiptHash === 'string' ? attempt.receiptHash : null,
|
|
172
|
+
}));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function buildSourceLineageReceipt({ portfolio, metadata = [], now = new Date() }) {
|
|
176
|
+
const metadataByArtifact = new Map(
|
|
177
|
+
list(metadata).map((item) => [artifactKey(item.ecosystem, item.name), item])
|
|
178
|
+
);
|
|
179
|
+
const artifacts = list(portfolio?.packages).map((row) => {
|
|
180
|
+
const source = metadataByArtifact.get(artifactKey(row.ecosystem, row.name)) || {};
|
|
181
|
+
const sourceRepository = normalizeRepositoryUrl(source.sourceRepository || source.repository);
|
|
182
|
+
return {
|
|
183
|
+
ecosystem: row.ecosystem,
|
|
184
|
+
name: row.name,
|
|
185
|
+
version: row.expectedVersion || row.observedVersion || source.version || null,
|
|
186
|
+
sourceRepository,
|
|
187
|
+
sourceDirectory: portableDirectory(source.sourceDirectory || source.directory),
|
|
188
|
+
registryStatus: Number.isInteger(source.registryStatus) ? source.registryStatus : null,
|
|
189
|
+
registryError: source.registryError ? String(source.registryError).slice(0, 240) : null,
|
|
190
|
+
integrity: typeof source.integrity === 'string' ? source.integrity : null,
|
|
191
|
+
sourceRevision: typeof source.sourceRevision === 'string' ? source.sourceRevision : null,
|
|
192
|
+
mapped: Boolean(sourceRepository),
|
|
193
|
+
};
|
|
194
|
+
});
|
|
195
|
+
const mapped = artifacts.filter((artifact) => artifact.mapped).length;
|
|
196
|
+
const receipt = {
|
|
197
|
+
schema: HOLOSYSTEM_LINEAGE_SCHEMA,
|
|
198
|
+
generatedAt: now.toISOString(),
|
|
199
|
+
status: mapped === artifacts.length ? 'complete' : 'partial',
|
|
200
|
+
summary: {
|
|
201
|
+
total: artifacts.length,
|
|
202
|
+
mapped,
|
|
203
|
+
gaps: artifacts.length - mapped,
|
|
204
|
+
},
|
|
205
|
+
artifacts,
|
|
206
|
+
boundaries: {
|
|
207
|
+
registryMetadataIsEvidence: true,
|
|
208
|
+
localPathsForbidden: true,
|
|
209
|
+
unknownLineageBlocksSourceClaims: true,
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
receipt.receiptHash = hashReceipt({ ...receipt, generatedAt: null });
|
|
213
|
+
return receipt;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function activeArtifactKeys(activeProofBatches) {
|
|
217
|
+
const keys = new Set();
|
|
218
|
+
for (const batch of list(activeProofBatches)) {
|
|
219
|
+
if (!['running', 'claimed', 'queued'].includes(batch?.status)) continue;
|
|
220
|
+
for (const item of list(batch.packages)) {
|
|
221
|
+
if (typeof item === 'string') keys.add(item.toLowerCase());
|
|
222
|
+
else keys.add(artifactKey(item?.ecosystem, item?.name));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return keys;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function selectNextConsumptionWork({
|
|
229
|
+
portfolio,
|
|
230
|
+
lineage,
|
|
231
|
+
activeProofBatches = [],
|
|
232
|
+
maxCandidates = 20,
|
|
233
|
+
now = new Date(),
|
|
234
|
+
}) {
|
|
235
|
+
const active = activeArtifactKeys(activeProofBatches);
|
|
236
|
+
const sourceByArtifact = new Map(
|
|
237
|
+
list(lineage?.artifacts).map((item) => [artifactKey(item.ecosystem, item.name), item])
|
|
238
|
+
);
|
|
239
|
+
const candidates = list(portfolio?.packages)
|
|
240
|
+
.filter((row) => row.classification !== 'passing')
|
|
241
|
+
.filter((row) => !active.has(artifactKey(row.ecosystem, row.name)))
|
|
242
|
+
.map((row) => {
|
|
243
|
+
const source = sourceByArtifact.get(artifactKey(row.ecosystem, row.name));
|
|
244
|
+
const basePriority = row.classification === 'failed' ? 400 : row.classification === 'stale' ? 300 : 200;
|
|
245
|
+
const priority = basePriority + (source?.mapped ? 0 : 25);
|
|
246
|
+
return {
|
|
247
|
+
ecosystem: row.ecosystem,
|
|
248
|
+
name: row.name,
|
|
249
|
+
version: row.expectedVersion || row.observedVersion || null,
|
|
250
|
+
classification: row.classification,
|
|
251
|
+
action: row.classification === 'failed'
|
|
252
|
+
? 'repair-consumer-contract'
|
|
253
|
+
: row.classification === 'stale'
|
|
254
|
+
? 'refresh-cold-consumption'
|
|
255
|
+
: 'prove-cold-consumption',
|
|
256
|
+
priority,
|
|
257
|
+
sourceRepository: source?.sourceRepository || null,
|
|
258
|
+
reasons: [
|
|
259
|
+
`classification:${row.classification}`,
|
|
260
|
+
...(source?.mapped ? [] : ['source-lineage-missing']),
|
|
261
|
+
...list(row.issues),
|
|
262
|
+
],
|
|
263
|
+
};
|
|
264
|
+
})
|
|
265
|
+
.sort((left, right) => right.priority - left.priority || artifactKey(left.ecosystem, left.name).localeCompare(artifactKey(right.ecosystem, right.name)))
|
|
266
|
+
.slice(0, Math.max(1, Math.min(Number(maxCandidates) || 20, 100)));
|
|
267
|
+
|
|
268
|
+
const receipt = {
|
|
269
|
+
schema: HOLOSYSTEM_NEXT_WORK_SCHEMA,
|
|
270
|
+
generatedAt: now.toISOString(),
|
|
271
|
+
status: candidates.length > 0 ? 'action-ready' : 'idle',
|
|
272
|
+
selected: candidates[0] || null,
|
|
273
|
+
candidates,
|
|
274
|
+
policy: {
|
|
275
|
+
mode: 'bounded',
|
|
276
|
+
maxCandidates: Math.max(1, Math.min(Number(maxCandidates) || 20, 100)),
|
|
277
|
+
excludesActiveBatches: true,
|
|
278
|
+
publishRequiresAuthority: true,
|
|
279
|
+
},
|
|
280
|
+
stopConditions: [...STOP_CONDITIONS],
|
|
281
|
+
};
|
|
282
|
+
receipt.receiptHash = hashReceipt({ ...receipt, generatedAt: null });
|
|
283
|
+
return receipt;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function buildConsumptionSurfaceCatalog({
|
|
287
|
+
seeds = {},
|
|
288
|
+
manifest = {},
|
|
289
|
+
portfolio = {},
|
|
290
|
+
github = [],
|
|
291
|
+
services = [],
|
|
292
|
+
containers = [],
|
|
293
|
+
mcpHealth = {},
|
|
294
|
+
skills = { ok: false, count: 0 },
|
|
295
|
+
lineage = null,
|
|
296
|
+
activeProofBatches = [],
|
|
297
|
+
promotionHistory = [],
|
|
298
|
+
evidence = {},
|
|
299
|
+
now = new Date(),
|
|
300
|
+
}) {
|
|
301
|
+
const evidenceRefs = {
|
|
302
|
+
operatingSet: evidence.operatingSet || 'package-manifest.json',
|
|
303
|
+
packageAdmission: evidence.packageAdmission || 'portfolio-consumer.json',
|
|
304
|
+
sourceLineage: evidence.sourceLineage || 'source-lineage.json',
|
|
305
|
+
};
|
|
306
|
+
const npm = packageRail(portfolio, manifest, 'npm', evidenceRefs.packageAdmission);
|
|
307
|
+
const pypi = packageRail(portfolio, manifest, 'pypi', evidenceRefs.packageAdmission);
|
|
308
|
+
const githubRail = countRail(github);
|
|
309
|
+
const serviceRail = countRail(services);
|
|
310
|
+
const containerRail = countRail(containers);
|
|
311
|
+
const sourceAudit = seeds?.mcp?.sourceAudit || {};
|
|
312
|
+
const deployedTools = Number(mcpHealth?.tools) || 0;
|
|
313
|
+
const expectedProducts = list(seeds?.github?.products);
|
|
314
|
+
const discoveryCurrent = portfolio?.scope?.registries?.npm?.declaredComplete === true
|
|
315
|
+
&& portfolio?.scope?.registries?.pypi?.declaredComplete === true
|
|
316
|
+
&& github.length === expectedProducts.length
|
|
317
|
+
&& github.every((item) => item.published)
|
|
318
|
+
&& skills.ok === true
|
|
319
|
+
&& deployedTools > 0;
|
|
320
|
+
const lineageSummary = lineage?.summary || {
|
|
321
|
+
total: list(portfolio?.packages).length,
|
|
322
|
+
mapped: 0,
|
|
323
|
+
gaps: list(portfolio?.packages).length,
|
|
324
|
+
};
|
|
325
|
+
const findings = [];
|
|
326
|
+
if (npm.gaps || pypi.gaps) findings.push({
|
|
327
|
+
id: 'package-cold-consumption-gaps',
|
|
328
|
+
severity: 'blocking',
|
|
329
|
+
count: npm.gaps + pypi.gaps,
|
|
330
|
+
evidence: evidenceRefs.packageAdmission,
|
|
331
|
+
});
|
|
332
|
+
if (lineageSummary.gaps) findings.push({
|
|
333
|
+
id: 'package-source-lineage-gaps',
|
|
334
|
+
severity: 'attention',
|
|
335
|
+
count: lineageSummary.gaps,
|
|
336
|
+
evidence: evidenceRefs.sourceLineage,
|
|
337
|
+
});
|
|
338
|
+
if (deployedTools !== Number(sourceAudit.tools || 0)) findings.push({
|
|
339
|
+
id: 'mcp-deploy-source-count-drift',
|
|
340
|
+
severity: 'attention',
|
|
341
|
+
deployedTools,
|
|
342
|
+
sourceTools: Number(sourceAudit.tools || 0),
|
|
343
|
+
});
|
|
344
|
+
if (Number(sourceAudit.orphanTools || 0)) findings.push({
|
|
345
|
+
id: 'agent-tool-surface-gaps',
|
|
346
|
+
severity: 'attention',
|
|
347
|
+
orphanTools: Number(sourceAudit.orphanTools || 0),
|
|
348
|
+
unbackedCoveredTools: Number(sourceAudit.unbackedCoveredTools || 0),
|
|
349
|
+
});
|
|
350
|
+
const nextWork = selectNextConsumptionWork({ portfolio, lineage, activeProofBatches, now });
|
|
351
|
+
const projectedProofBatches = projectProofBatches(activeProofBatches);
|
|
352
|
+
const projectedPromotionHistory = projectPromotionHistory(promotionHistory);
|
|
353
|
+
const receipt = {
|
|
354
|
+
schema: HOLOSYSTEM_CATALOG_SCHEMA,
|
|
355
|
+
generatedAt: now.toISOString(),
|
|
356
|
+
status: discoveryCurrent ? 'current' : 'incomplete-discovery',
|
|
357
|
+
rule: 'Never collapse unlike artifacts into one total. Published, consumer-ready, and dogfooded are separate states on every rail.',
|
|
358
|
+
rails: {
|
|
359
|
+
npm,
|
|
360
|
+
pypi,
|
|
361
|
+
github: { ...githubRail, dogfooded: 0, evidence: 'public GitHub repository metadata plus README' },
|
|
362
|
+
services: { ...serviceRail, dogfooded: null, evidence: 'public health endpoints' },
|
|
363
|
+
containers: { ...containerRail, dogfooded: null, evidence: 'public container package pages' },
|
|
364
|
+
},
|
|
365
|
+
packageFailures: packageFailureCategories(portfolio),
|
|
366
|
+
lineage: {
|
|
367
|
+
...lineageSummary,
|
|
368
|
+
status: lineage?.status || 'unproven',
|
|
369
|
+
evidence: evidenceRefs.sourceLineage,
|
|
370
|
+
},
|
|
371
|
+
agentSurface: {
|
|
372
|
+
deployedMcpTools: deployedTools,
|
|
373
|
+
sourceMappedTools: Number(sourceAudit.tools || 0),
|
|
374
|
+
sourceCoveredTools: Number(sourceAudit.coveredTools || 0),
|
|
375
|
+
orphanTools: Number(sourceAudit.orphanTools || 0),
|
|
376
|
+
unbackedCoveredTools: Number(sourceAudit.unbackedCoveredTools || 0),
|
|
377
|
+
publicRoutes: Number(sourceAudit.routes || 0),
|
|
378
|
+
publicSkills: Number(skills.count || 0),
|
|
379
|
+
mcpVersion: mcpHealth?.version || null,
|
|
380
|
+
evidence: 'public MCP health plus public repository tree and capability audit',
|
|
381
|
+
},
|
|
382
|
+
activity: {
|
|
383
|
+
activeProofBatches: projectedProofBatches,
|
|
384
|
+
promotionHistory: projectedPromotionHistory,
|
|
385
|
+
nextWork,
|
|
386
|
+
},
|
|
387
|
+
items: { github: clone(github), services: clone(services), containers: clone(containers) },
|
|
388
|
+
findings,
|
|
389
|
+
boundaries: {
|
|
390
|
+
...evidenceRefs,
|
|
391
|
+
noGrandTotal: true,
|
|
392
|
+
privateSourceFoldersAreNotPublishedArtifacts: true,
|
|
393
|
+
callerOwnsCredentialsAndPolicy: true,
|
|
394
|
+
},
|
|
395
|
+
};
|
|
396
|
+
receipt.receiptHash = hashReceipt({ ...receipt, generatedAt: null });
|
|
397
|
+
return receipt;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
async function request(fetchImpl, url, { json = true } = {}) {
|
|
401
|
+
try {
|
|
402
|
+
const response = await fetchImpl(url, {
|
|
403
|
+
headers: {
|
|
404
|
+
accept: json ? 'application/json' : 'text/html,application/xhtml+xml',
|
|
405
|
+
'user-agent': 'holoscript-holosystem-consumer/1',
|
|
406
|
+
},
|
|
407
|
+
redirect: 'follow',
|
|
408
|
+
signal: AbortSignal.timeout(20_000),
|
|
409
|
+
});
|
|
410
|
+
let body = null;
|
|
411
|
+
if (response.ok && json) {
|
|
412
|
+
try {
|
|
413
|
+
body = await response.json();
|
|
414
|
+
} catch {
|
|
415
|
+
body = null;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return { ok: response.ok, status: response.status, body };
|
|
419
|
+
} catch (error) {
|
|
420
|
+
return {
|
|
421
|
+
ok: false,
|
|
422
|
+
status: null,
|
|
423
|
+
body: null,
|
|
424
|
+
error: String(error?.message || error).slice(0, 240),
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async function mapConcurrent(items, concurrency, worker) {
|
|
430
|
+
const results = new Array(items.length);
|
|
431
|
+
let index = 0;
|
|
432
|
+
async function runWorker() {
|
|
433
|
+
while (index < items.length) {
|
|
434
|
+
const current = index;
|
|
435
|
+
index += 1;
|
|
436
|
+
results[current] = await worker(items[current], current);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
const count = Math.max(1, Math.min(Number(concurrency) || 6, 12, items.length || 1));
|
|
440
|
+
await Promise.all(Array.from({ length: count }, () => runWorker()));
|
|
441
|
+
return results;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function pypiRepository(info) {
|
|
445
|
+
const urls = info?.project_urls || {};
|
|
446
|
+
for (const key of ['Source', 'Source Code', 'Repository', 'Code', 'Homepage']) {
|
|
447
|
+
const value = normalizeRepositoryUrl(urls[key]);
|
|
448
|
+
if (value) return value;
|
|
449
|
+
}
|
|
450
|
+
return normalizeRepositoryUrl(info?.home_page);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export async function discoverSourceLineage({
|
|
454
|
+
portfolio,
|
|
455
|
+
fetchImpl = globalThis.fetch,
|
|
456
|
+
concurrency = 6,
|
|
457
|
+
now = new Date(),
|
|
458
|
+
}) {
|
|
459
|
+
if (typeof fetchImpl !== 'function') throw new TypeError('A fetch implementation is required.');
|
|
460
|
+
const metadata = await mapConcurrent(list(portfolio?.packages), concurrency, async (artifact) => {
|
|
461
|
+
const version = artifact.expectedVersion || artifact.observedVersion;
|
|
462
|
+
if (artifact.ecosystem === 'npm') {
|
|
463
|
+
const target = version ? `${encodeURIComponent(artifact.name)}/${encodeURIComponent(version)}` : encodeURIComponent(artifact.name);
|
|
464
|
+
const response = await request(fetchImpl, `https://registry.npmjs.org/${target}`);
|
|
465
|
+
return {
|
|
466
|
+
ecosystem: 'npm',
|
|
467
|
+
name: artifact.name,
|
|
468
|
+
version: response.body?.version || version || null,
|
|
469
|
+
sourceRepository: normalizeRepositoryUrl(response.body?.repository),
|
|
470
|
+
sourceDirectory: portableDirectory(response.body?.repository?.directory),
|
|
471
|
+
registryStatus: response.status,
|
|
472
|
+
registryError: response.error || null,
|
|
473
|
+
integrity: response.body?.dist?.integrity || null,
|
|
474
|
+
sourceRevision: response.body?.gitHead || null,
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
const suffix = version ? `/${encodeURIComponent(version)}` : '';
|
|
478
|
+
const response = await request(fetchImpl, `https://pypi.org/pypi/${encodeURIComponent(artifact.name)}${suffix}/json`);
|
|
479
|
+
const files = list(response.body?.urls);
|
|
480
|
+
const digest = files.map((file) => file?.digests?.sha256).find(Boolean);
|
|
481
|
+
return {
|
|
482
|
+
ecosystem: 'pypi',
|
|
483
|
+
name: artifact.name,
|
|
484
|
+
version: response.body?.info?.version || version || null,
|
|
485
|
+
sourceRepository: pypiRepository(response.body?.info),
|
|
486
|
+
sourceDirectory: null,
|
|
487
|
+
registryStatus: response.status,
|
|
488
|
+
registryError: response.error || null,
|
|
489
|
+
integrity: digest ? `sha256:${digest}` : null,
|
|
490
|
+
sourceRevision: null,
|
|
491
|
+
};
|
|
492
|
+
});
|
|
493
|
+
return buildSourceLineageReceipt({ portfolio, metadata, now });
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
export async function discoverConsumptionSurfaceCatalog({
|
|
497
|
+
seeds,
|
|
498
|
+
manifest,
|
|
499
|
+
portfolio,
|
|
500
|
+
lineage = null,
|
|
501
|
+
activeProofBatches = [],
|
|
502
|
+
promotionHistory = [],
|
|
503
|
+
evidence = {},
|
|
504
|
+
fetchImpl = globalThis.fetch,
|
|
505
|
+
now = new Date(),
|
|
506
|
+
}) {
|
|
507
|
+
if (typeof fetchImpl !== 'function') throw new TypeError('A fetch implementation is required.');
|
|
508
|
+
const github = await Promise.all(list(seeds?.github?.products).map(async (fullName) => {
|
|
509
|
+
const [metadata, readme] = await Promise.all([
|
|
510
|
+
request(fetchImpl, `https://api.github.com/repos/${fullName}`),
|
|
511
|
+
request(fetchImpl, `https://api.github.com/repos/${fullName}/readme`),
|
|
512
|
+
]);
|
|
513
|
+
const repository = metadata.body || {};
|
|
514
|
+
const published = metadata.ok && repository.private === false;
|
|
515
|
+
return {
|
|
516
|
+
name: fullName,
|
|
517
|
+
published,
|
|
518
|
+
contractReady: published
|
|
519
|
+
&& repository.archived !== true
|
|
520
|
+
&& repository.fork !== true
|
|
521
|
+
&& Boolean(repository.description)
|
|
522
|
+
&& Boolean(repository.license?.spdx_id)
|
|
523
|
+
&& readme.ok,
|
|
524
|
+
defaultBranch: repository.default_branch || null,
|
|
525
|
+
license: repository.license?.spdx_id || null,
|
|
526
|
+
readme: readme.ok,
|
|
527
|
+
archived: repository.archived === true,
|
|
528
|
+
fork: repository.fork === true,
|
|
529
|
+
url: repository.html_url || `https://github.com/${fullName}`,
|
|
530
|
+
};
|
|
531
|
+
}));
|
|
532
|
+
const services = await Promise.all(list(seeds?.services).map(async (service) => {
|
|
533
|
+
const probe = await request(fetchImpl, `${service.url}${service.health}`);
|
|
534
|
+
return {
|
|
535
|
+
name: service.name,
|
|
536
|
+
url: service.url,
|
|
537
|
+
health: service.health,
|
|
538
|
+
published: true,
|
|
539
|
+
contractReady: probe.ok,
|
|
540
|
+
status: probe.status,
|
|
541
|
+
};
|
|
542
|
+
}));
|
|
543
|
+
const containers = await Promise.all(list(seeds?.containers).map(async (container) => {
|
|
544
|
+
const page = await request(
|
|
545
|
+
fetchImpl,
|
|
546
|
+
`https://github.com/users/${container.owner}/packages/container/package/${container.name}`,
|
|
547
|
+
{ json: false }
|
|
548
|
+
);
|
|
549
|
+
return {
|
|
550
|
+
name: container.name,
|
|
551
|
+
image: container.image,
|
|
552
|
+
published: page.ok,
|
|
553
|
+
contractReady: page.ok,
|
|
554
|
+
status: page.status,
|
|
555
|
+
};
|
|
556
|
+
}));
|
|
557
|
+
const [mcp, repository] = await Promise.all([
|
|
558
|
+
request(fetchImpl, seeds?.mcp?.healthUrl),
|
|
559
|
+
request(fetchImpl, `https://api.github.com/repos/${seeds?.mcp?.publicRepository}`),
|
|
560
|
+
]);
|
|
561
|
+
const branch = repository.body?.default_branch || 'main';
|
|
562
|
+
const tree = await request(
|
|
563
|
+
fetchImpl,
|
|
564
|
+
`https://api.github.com/repos/${seeds?.mcp?.publicRepository}/git/trees/${branch}?recursive=1`
|
|
565
|
+
);
|
|
566
|
+
const paths = list(tree.body?.tree).map((item) => item.path);
|
|
567
|
+
const skills = {
|
|
568
|
+
ok: repository.ok && tree.ok && tree.body?.truncated !== true,
|
|
569
|
+
count: paths.filter((path) => /(?:^|\/)SKILL\.md$/u.test(path)).length,
|
|
570
|
+
treeTruncated: tree.body?.truncated === true,
|
|
571
|
+
};
|
|
572
|
+
return buildConsumptionSurfaceCatalog({
|
|
573
|
+
seeds,
|
|
574
|
+
manifest,
|
|
575
|
+
portfolio,
|
|
576
|
+
github,
|
|
577
|
+
services,
|
|
578
|
+
containers,
|
|
579
|
+
mcpHealth: mcp.body,
|
|
580
|
+
skills,
|
|
581
|
+
lineage,
|
|
582
|
+
activeProofBatches,
|
|
583
|
+
promotionHistory,
|
|
584
|
+
evidence,
|
|
585
|
+
now,
|
|
586
|
+
});
|
|
587
|
+
}
|
package/src/index.mjs
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
export const HOLOSYSTEM_CONFIG_SCHEMA = 'holoscript.holosystem.consumer.v1';
|
|
2
2
|
export const HOLOSYSTEM_INSPECTION_SCHEMA = 'holoscript.holosystem.inspection.v1';
|
|
3
3
|
|
|
4
|
+
export {
|
|
5
|
+
HOLOSYSTEM_CATALOG_SCHEMA,
|
|
6
|
+
HOLOSYSTEM_CONSUMER_INPUT_SCHEMA,
|
|
7
|
+
HOLOSYSTEM_LINEAGE_SCHEMA,
|
|
8
|
+
HOLOSYSTEM_NEXT_WORK_SCHEMA,
|
|
9
|
+
buildConsumptionSurfaceCatalog,
|
|
10
|
+
buildSourceLineageReceipt,
|
|
11
|
+
discoverConsumptionSurfaceCatalog,
|
|
12
|
+
discoverSourceLineage,
|
|
13
|
+
hashConsumerInput,
|
|
14
|
+
inspectPublicDependencySpecs,
|
|
15
|
+
normalizeRepositoryUrl,
|
|
16
|
+
selectNextConsumptionWork,
|
|
17
|
+
} from './catalog.mjs';
|
|
18
|
+
|
|
4
19
|
const REQUIRED_CONTRACTS = [
|
|
5
20
|
{ registry: 'npm', role: 'agent-runtime' },
|
|
6
21
|
{ registry: 'npm', role: 'memory' },
|