@holoscript/holosystem 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 HoloScript ecosystem (Joseph, founder).
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # @holoscript/holosystem
2
+
3
+ Create and inspect a portable HoloSystem consumer configuration from public npm
4
+ and PyPI package contracts. The package is for external founders, agent
5
+ operators, and cold consumers who need a small bootstrap contract without a
6
+ private workspace or machine-specific defaults.
7
+
8
+ ```bash
9
+ npm install @holoscript/holosystem
10
+ npx holosystem create --id acme-founder --workspace acme --json
11
+ npx holosystem inspect holosystem.config.json --json
12
+ ```
13
+
14
+ ## What It Creates
15
+
16
+ The default configuration pins public contracts for:
17
+
18
+ - `@holoscript/agent-runtime` for provider-neutral agent execution;
19
+ - `@holoscript/memory` for caller-owned memory;
20
+ - `@holoscript/holorepo` for JavaScript repository, database, and knowledge custody;
21
+ - `holoscript-holorepo` for the matching Python custody surface.
22
+
23
+ It also names caller-owned environment bindings for storage, memory, knowledge,
24
+ and authority. Only environment key names are stored. Credential values,
25
+ connection details, and private operator state are never configuration fields.
26
+
27
+ ## API
28
+
29
+ ```js
30
+ import { createHoloSystemConfig, inspectHoloSystemConfig } from '@holoscript/holosystem';
31
+
32
+ const config = createHoloSystemConfig({
33
+ consumerId: 'acme-founder',
34
+ workspace: 'acme',
35
+ });
36
+
37
+ const report = inspectHoloSystemConfig(config);
38
+ if (!report.ready) {
39
+ console.error(report.errors);
40
+ process.exitCode = 2;
41
+ }
42
+ ```
43
+
44
+ `createHoloSystemConfig(options?)` returns deterministic JSON data. Callers may
45
+ supply their own exact public package contracts and caller-owned binding map.
46
+ The function fails closed when its result does not pass inspection.
47
+
48
+ `inspectHoloSystemConfig(config)` is pure and secret-safe. It reports named
49
+ checks for schema, identity, public contracts, caller bindings, portability,
50
+ bounded operations, and fresh receipt policy. Reports summarize package names,
51
+ versions, roles, and environment key names; they do not echo unknown config
52
+ values.
53
+
54
+ ## CLI
55
+
56
+ ```bash
57
+ # Write holosystem.config.json. Existing files require explicit --force.
58
+ npx holosystem create --id acme-founder --workspace acme
59
+
60
+ # Send the config to another agent without writing a file.
61
+ npx holosystem create --stdout
62
+
63
+ # Human-readable gate output.
64
+ npx holosystem inspect holosystem.config.json
65
+
66
+ # Machine-readable receipt. Exit code 2 means the config failed inspection.
67
+ npx holosystem inspect holosystem.config.json --json
68
+
69
+ # Pipeline input is supported.
70
+ npx holosystem create --stdout | npx holosystem inspect - --json
71
+ ```
72
+
73
+ Creation never installs packages, connects to storage, acquires credentials, or
74
+ mutates services. Those actions belong to the selected public packages and the
75
+ operator's own adapters. The CLI only writes the requested configuration file;
76
+ inspection is read-only.
77
+
78
+ ## Consumption Contract
79
+
80
+ Consumers bring their own configuration, credentials, Postgres storage,
81
+ knowledge and authority adapters, receipt sink, and runtime host. Environment
82
+ variables reference that caller-owned state. The package does not ship or
83
+ assume founder-local paths, private repositories, private process, a specific
84
+ machine, or a hosted provider.
85
+
86
+ The portability gate rejects:
87
+
88
+ - local, Git, URL, or floating package specs instead of pinned registry releases;
89
+ - missing npm or PyPI custody roles;
90
+ - absolute receipt paths or parent traversal;
91
+ - embedded password, secret, token, private-key, or connection-string fields;
92
+ - non-caller binding ownership;
93
+ - unbounded autonomy, missing stop conditions, or stale receipt policy.
94
+
95
+ Agent-operable evidence is the `inspect --json` receipt. Before release, run the
96
+ package check, an npm pack dry run, and the repository public-consumption gate.
97
+
98
+ ## Release Boundary
99
+
100
+ Release lane: `v0-preview`. Supported behavior is deterministic config creation
101
+ and static, secret-safe portability inspection for the documented v1 schema.
102
+ Known limitations: this package does not verify registry availability, install
103
+ dependencies, test live credentials, bootstrap databases, or authorize agent
104
+ work. Roll back by pinning the previous package version and retaining the
105
+ consumer-owned config and receipts.
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { resolve } from 'node:path';
4
+
5
+ import { createHoloSystemConfig, inspectHoloSystemConfig } from '../src/index.mjs';
6
+
7
+ const CLI_RECEIPT_SCHEMA = 'holoscript.holosystem.cli-receipt.v1';
8
+ const HELP = `holosystem - create or inspect a portable HoloSystem consumer config
9
+
10
+ Usage:
11
+ holosystem create [--id <id>] [--workspace <id>] [--output <file>] [--force] [--json]
12
+ holosystem create --stdout
13
+ holosystem inspect [file|-] [--json]
14
+ holosystem --help
15
+ holosystem --version
16
+
17
+ Defaults:
18
+ create writes holosystem.config.json and never overwrites it without --force.
19
+ inspect reads holosystem.config.json. Use - to read JSON from stdin.
20
+ `;
21
+
22
+ function die(message, { json = false, code = 1 } = {}) {
23
+ if (json) {
24
+ process.stderr.write(
25
+ `${JSON.stringify({ schema: CLI_RECEIPT_SCHEMA, ok: false, error: message }, null, 2)}\n`
26
+ );
27
+ } else {
28
+ process.stderr.write(`[holosystem] ${message}\n`);
29
+ }
30
+ process.exit(code);
31
+ }
32
+
33
+ function parseArguments(args, specification) {
34
+ const options = {};
35
+ const positionals = [];
36
+ for (let index = 0; index < args.length; index += 1) {
37
+ const item = args[index];
38
+ if (!item.startsWith('--')) {
39
+ positionals.push(item);
40
+ continue;
41
+ }
42
+ const equals = item.indexOf('=');
43
+ const name = item.slice(2, equals === -1 ? undefined : equals);
44
+ const type = specification[name];
45
+ if (!type) throw new Error(`Unknown option --${name}.`);
46
+ if (type === 'boolean') {
47
+ if (equals !== -1) throw new Error(`Option --${name} does not accept a value.`);
48
+ options[name] = true;
49
+ continue;
50
+ }
51
+ const value = equals === -1 ? args[index + 1] : item.slice(equals + 1);
52
+ if (!value || value.startsWith('--')) throw new Error(`Option --${name} requires a value.`);
53
+ options[name] = value;
54
+ if (equals === -1) index += 1;
55
+ }
56
+ return { options, positionals };
57
+ }
58
+
59
+ function receipt(operation, fields) {
60
+ return {
61
+ schema: CLI_RECEIPT_SCHEMA,
62
+ generatedAt: new Date().toISOString(),
63
+ operation,
64
+ ...fields,
65
+ };
66
+ }
67
+
68
+ function outputJson(value) {
69
+ process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
70
+ }
71
+
72
+ function runCreate(args) {
73
+ let parsed;
74
+ try {
75
+ parsed = parseArguments(args, {
76
+ id: 'value',
77
+ workspace: 'value',
78
+ output: 'value',
79
+ force: 'boolean',
80
+ stdout: 'boolean',
81
+ json: 'boolean',
82
+ });
83
+ } catch (error) {
84
+ die(error.message, { json: args.includes('--json') });
85
+ }
86
+ const { options, positionals } = parsed;
87
+ if (positionals.length > 0)
88
+ die('create does not accept positional arguments.', { json: options.json });
89
+ if (options.stdout && options.output) {
90
+ die('--stdout and --output cannot be combined.', { json: options.json });
91
+ }
92
+ if (options.stdout && options.json) {
93
+ die('--stdout already emits JSON config and cannot be combined with --json.', { json: true });
94
+ }
95
+
96
+ let config;
97
+ try {
98
+ config = createHoloSystemConfig({
99
+ consumerId: options.id || 'external-founder',
100
+ workspace: options.workspace || 'default',
101
+ });
102
+ } catch (error) {
103
+ die(error.message, { json: options.json, code: 2 });
104
+ }
105
+
106
+ if (options.stdout) {
107
+ outputJson(config);
108
+ return;
109
+ }
110
+
111
+ const output = options.output || 'holosystem.config.json';
112
+ const absoluteOutput = resolve(process.cwd(), output);
113
+ if (existsSync(absoluteOutput) && !options.force) {
114
+ die(`${output} already exists; use --force to replace it.`, { json: options.json });
115
+ }
116
+ writeFileSync(absoluteOutput, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
117
+ const report = inspectHoloSystemConfig(config);
118
+ const result = receipt('create', { ok: report.ready, output, report });
119
+ if (options.json) outputJson(result);
120
+ else process.stdout.write(`Created ${output}\nPortable: ${report.portable ? 'yes' : 'no'}\n`);
121
+ }
122
+
123
+ function readStdin() {
124
+ return readFileSync(0, 'utf8');
125
+ }
126
+
127
+ function runInspect(args) {
128
+ let parsed;
129
+ try {
130
+ parsed = parseArguments(args, { json: 'boolean' });
131
+ } catch (error) {
132
+ die(error.message, { json: args.includes('--json') });
133
+ }
134
+ const { options, positionals } = parsed;
135
+ if (positionals.length > 1) die('inspect accepts at most one file.', { json: options.json });
136
+ const input = positionals[0] || 'holosystem.config.json';
137
+ let config;
138
+ try {
139
+ const source =
140
+ input === '-' ? readStdin() : readFileSync(resolve(process.cwd(), input), 'utf8');
141
+ config = JSON.parse(source.replace(/^\uFEFF/u, ''));
142
+ } catch (error) {
143
+ die(`Cannot read ${input}: ${error.message}`, { json: options.json });
144
+ }
145
+
146
+ const report = inspectHoloSystemConfig(config);
147
+ if (options.json) outputJson(receipt('inspect', { ok: report.ready, input, report }));
148
+ else {
149
+ process.stdout.write(
150
+ `Ready: ${report.ready ? 'yes' : 'no'}\nPortable: ${report.portable ? 'yes' : 'no'}\n`
151
+ );
152
+ for (const check of report.checks) {
153
+ process.stdout.write(`${check.ok ? 'PASS' : 'FAIL'} ${check.id}\n`);
154
+ }
155
+ for (const error of report.errors) process.stdout.write(` ${error.path}: ${error.message}\n`);
156
+ }
157
+ if (!report.ready) process.exitCode = 2;
158
+ }
159
+
160
+ const argv = process.argv.slice(2);
161
+ const command = argv[0];
162
+ if (!command || command === '--help' || command === '-h' || command === 'help') {
163
+ process.stdout.write(HELP);
164
+ } else if (command === '--version' || command === '-v') {
165
+ const manifest = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
166
+ process.stdout.write(`${manifest.version}\n`);
167
+ } else if (command === 'create') {
168
+ runCreate(argv.slice(1));
169
+ } else if (command === 'inspect') {
170
+ runInspect(argv.slice(1));
171
+ } else {
172
+ die(`Unknown command ${command}. Run holosystem --help.`);
173
+ }
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@holoscript/holosystem",
3
+ "version": "0.1.0",
4
+ "description": "Portable public-package bootstrap and inspection contract for HoloSystem consumers.",
5
+ "releaseLane": "v0-preview",
6
+ "keywords": [
7
+ "holoscript",
8
+ "holosystem",
9
+ "agent-runtime",
10
+ "sovereign",
11
+ "bootstrap",
12
+ "portable-config"
13
+ ],
14
+ "license": "MIT",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/brianonbased-dev/HoloScript.git",
18
+ "directory": "packages/holosystem"
19
+ },
20
+ "homepage": "https://github.com/brianonbased-dev/HoloScript/tree/main/packages/holosystem",
21
+ "bugs": {
22
+ "url": "https://github.com/brianonbased-dev/HoloScript/issues"
23
+ },
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "type": "module",
28
+ "exports": {
29
+ ".": "./src/index.mjs"
30
+ },
31
+ "bin": {
32
+ "holosystem": "./bin/holosystem.mjs"
33
+ },
34
+ "files": [
35
+ "bin",
36
+ "src",
37
+ "README.md",
38
+ "LICENSE"
39
+ ],
40
+ "engines": {
41
+ "node": ">=18"
42
+ },
43
+ "scripts": {
44
+ "build": "node --check src/index.mjs && node --check bin/holosystem.mjs",
45
+ "test": "node --test test/holosystem.test.mjs test/cli.test.mjs",
46
+ "smoke": "node bin/holosystem.mjs --help",
47
+ "check": "pnpm run build && pnpm run test && pnpm run smoke",
48
+ "prepublishOnly": "pnpm run check"
49
+ },
50
+ "holorepo": {
51
+ "releaseLane": "v0-preview",
52
+ "pypiCompanion": "holoscript-holorepo",
53
+ "publicConsumption": {
54
+ "coreRule": "Cold consumers create and inspect a HoloSystem configuration from public registry contracts and caller-owned bindings.",
55
+ "localBoundary": "Machine paths, credentials, private repositories, and operator state are caller inputs, never package defaults.",
56
+ "supportBoundary": "Configuration creation and static portability inspection are supported; installation, credential acquisition, service mutation, and publishing are outside this package."
57
+ }
58
+ }
59
+ }
package/src/index.mjs ADDED
@@ -0,0 +1,574 @@
1
+ export const HOLOSYSTEM_CONFIG_SCHEMA = 'holoscript.holosystem.consumer.v1';
2
+ export const HOLOSYSTEM_INSPECTION_SCHEMA = 'holoscript.holosystem.inspection.v1';
3
+
4
+ const REQUIRED_CONTRACTS = [
5
+ { registry: 'npm', role: 'agent-runtime' },
6
+ { registry: 'npm', role: 'memory' },
7
+ { registry: 'npm', role: 'repo-custody' },
8
+ { registry: 'pypi', role: 'repo-custody' },
9
+ ];
10
+
11
+ const REQUIRED_BINDINGS = ['storage', 'memory', 'knowledge', 'authority'];
12
+ const REQUIRED_STOP_CONDITIONS = ['authority-required', 'validation-failed', 'lease-expired'];
13
+ const KNOWN_TOP_LEVEL_KEYS = new Set(['schema', 'consumer', 'contracts', 'bindings', 'operations']);
14
+
15
+ function deepFreeze(value) {
16
+ if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
17
+ Object.freeze(value);
18
+ for (const child of Object.values(value)) deepFreeze(child);
19
+ return value;
20
+ }
21
+
22
+ function clone(value) {
23
+ return JSON.parse(JSON.stringify(value));
24
+ }
25
+
26
+ export const DEFAULT_PUBLIC_PACKAGE_CONTRACTS = deepFreeze([
27
+ {
28
+ registry: 'npm',
29
+ name: '@holoscript/agent-runtime',
30
+ version: '0.6.1',
31
+ role: 'agent-runtime',
32
+ },
33
+ {
34
+ registry: 'npm',
35
+ name: '@holoscript/memory',
36
+ version: '0.3.0',
37
+ role: 'memory',
38
+ },
39
+ {
40
+ registry: 'npm',
41
+ name: '@holoscript/holorepo',
42
+ version: '0.3.7',
43
+ role: 'repo-custody',
44
+ },
45
+ {
46
+ registry: 'pypi',
47
+ name: 'holoscript-holorepo',
48
+ version: '0.3.7',
49
+ role: 'repo-custody',
50
+ },
51
+ ]);
52
+
53
+ export const DEFAULT_CALLER_BINDINGS = deepFreeze({
54
+ storage: { owner: 'caller', env: 'HOLOREPO_DATABASE_URL' },
55
+ memory: { owner: 'caller', env: 'MEMORY_DATABASE_URL' },
56
+ knowledge: { owner: 'caller', env: 'HOLOREPO_DATABASE_URL' },
57
+ authority: { owner: 'caller', env: 'HOLOSYSTEM_AUTHORITY_REF' },
58
+ });
59
+
60
+ const DEFAULT_OPERATIONS = deepFreeze({
61
+ autonomy: {
62
+ mode: 'bounded',
63
+ maxAttempts: 3,
64
+ leaseSeconds: 300,
65
+ stopConditions: REQUIRED_STOP_CONDITIONS,
66
+ },
67
+ receipts: {
68
+ directory: '.holosystem/receipts',
69
+ format: 'json',
70
+ maxAgeSeconds: 900,
71
+ },
72
+ });
73
+
74
+ function issue(list, check, code, path, message) {
75
+ list.push({ check, code, path, message });
76
+ }
77
+
78
+ function checkKnownKeys(value, allowed, path, check, errors) {
79
+ if (!isRecord(value)) return;
80
+ for (const key of Object.keys(value)) {
81
+ if (!allowed.has(key)) {
82
+ issue(
83
+ errors,
84
+ check,
85
+ 'unknown-field',
86
+ path ? `${path}.${key}` : key,
87
+ `Field ${key} is not part of the v1 consumer contract.`
88
+ );
89
+ }
90
+ }
91
+ }
92
+
93
+ function isRecord(value) {
94
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
95
+ }
96
+
97
+ function validId(value) {
98
+ return typeof value === 'string' && /^[a-z0-9][a-z0-9._-]{0,63}$/iu.test(value);
99
+ }
100
+
101
+ function validEnvName(value) {
102
+ return typeof value === 'string' && /^[A-Z][A-Z0-9_]*$/u.test(value);
103
+ }
104
+
105
+ function validPackageName(registry, value) {
106
+ if (typeof value !== 'string') return false;
107
+ if (registry === 'npm') {
108
+ return /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/u.test(value);
109
+ }
110
+ return registry === 'pypi' && /^[a-z0-9]+(?:[-_.][a-z0-9]+)*$/iu.test(value);
111
+ }
112
+
113
+ function validPinnedVersion(value) {
114
+ return (
115
+ typeof value === 'string' &&
116
+ /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u.test(value) &&
117
+ !/^(?:workspace|file|link|portal|git|https?):/iu.test(value)
118
+ );
119
+ }
120
+
121
+ function isPortablePath(value) {
122
+ return (
123
+ typeof value === 'string' &&
124
+ value.length > 0 &&
125
+ !/^[A-Za-z]:[\\/]/u.test(value) &&
126
+ !/^(?:[\\/]{2}|\/|~[\\/])/u.test(value) &&
127
+ !/(?:^|[\\/])\.\.(?:[\\/]|$)/u.test(value)
128
+ );
129
+ }
130
+
131
+ function inspectSensitiveValues(value, path, errors) {
132
+ if (Array.isArray(value)) {
133
+ value.forEach((child, index) => inspectSensitiveValues(child, `${path}[${index}]`, errors));
134
+ return;
135
+ }
136
+ if (!isRecord(value)) return;
137
+
138
+ for (const [key, child] of Object.entries(value)) {
139
+ const childPath = path ? `${path}.${key}` : key;
140
+ if (/(?:password|secret|token|apiKey|privateKey|connectionString)$/iu.test(key)) {
141
+ issue(
142
+ errors,
143
+ 'portable-inputs',
144
+ 'embedded-sensitive-field',
145
+ childPath,
146
+ 'Credential values are not configuration fields; reference a caller-owned environment key.'
147
+ );
148
+ }
149
+ inspectSensitiveValues(child, childPath, errors);
150
+ }
151
+ }
152
+
153
+ function checkSchema(config, errors) {
154
+ if (config.schema !== HOLOSYSTEM_CONFIG_SCHEMA) {
155
+ issue(errors, 'schema', 'schema-mismatch', 'schema', `Expected ${HOLOSYSTEM_CONFIG_SCHEMA}.`);
156
+ }
157
+ }
158
+
159
+ function checkConsumer(config, errors) {
160
+ if (!isRecord(config.consumer)) {
161
+ issue(errors, 'consumer', 'consumer-missing', 'consumer', 'Consumer identity is required.');
162
+ return;
163
+ }
164
+ checkKnownKeys(config.consumer, new Set(['id', 'workspace']), 'consumer', 'consumer', errors);
165
+ if (!validId(config.consumer.id)) {
166
+ issue(
167
+ errors,
168
+ 'consumer',
169
+ 'consumer-id-invalid',
170
+ 'consumer.id',
171
+ 'Consumer id must be a portable identifier of 1 to 64 characters.'
172
+ );
173
+ }
174
+ if (!validId(config.consumer.workspace)) {
175
+ issue(
176
+ errors,
177
+ 'consumer',
178
+ 'workspace-id-invalid',
179
+ 'consumer.workspace',
180
+ 'Workspace must be a portable identifier of 1 to 64 characters.'
181
+ );
182
+ }
183
+ }
184
+
185
+ function checkContracts(config, errors) {
186
+ if (!Array.isArray(config.contracts)) {
187
+ issue(
188
+ errors,
189
+ 'public-contracts',
190
+ 'contracts-missing',
191
+ 'contracts',
192
+ 'Public package contracts must be an array.'
193
+ );
194
+ return [];
195
+ }
196
+
197
+ const contracts = [];
198
+ const identities = new Set();
199
+ config.contracts.forEach((contract, index) => {
200
+ const path = `contracts[${index}]`;
201
+ if (!isRecord(contract)) {
202
+ issue(errors, 'public-contracts', 'contract-invalid', path, 'Contract must be an object.');
203
+ return;
204
+ }
205
+ checkKnownKeys(
206
+ contract,
207
+ new Set(['registry', 'name', 'version', 'role']),
208
+ path,
209
+ 'public-contracts',
210
+ errors
211
+ );
212
+ const registry = contract.registry;
213
+ if (registry !== 'npm' && registry !== 'pypi') {
214
+ issue(
215
+ errors,
216
+ 'public-contracts',
217
+ 'registry-invalid',
218
+ `${path}.registry`,
219
+ 'Registry must be npm or pypi.'
220
+ );
221
+ }
222
+ if (!validPackageName(registry, contract.name)) {
223
+ issue(
224
+ errors,
225
+ 'public-contracts',
226
+ 'package-name-invalid',
227
+ `${path}.name`,
228
+ 'Package name is invalid for its public registry.'
229
+ );
230
+ }
231
+ if (!validPinnedVersion(contract.version)) {
232
+ issue(
233
+ errors,
234
+ 'public-contracts',
235
+ 'version-not-pinned',
236
+ `${path}.version`,
237
+ 'Version must be an exact public release, not a local, remote, or floating spec.'
238
+ );
239
+ }
240
+ if (!validId(contract.role)) {
241
+ issue(
242
+ errors,
243
+ 'public-contracts',
244
+ 'role-invalid',
245
+ `${path}.role`,
246
+ 'Role must be a portable identifier.'
247
+ );
248
+ }
249
+ const identity = `${registry}:${String(contract.name).toLowerCase()}`;
250
+ if (identities.has(identity)) {
251
+ issue(
252
+ errors,
253
+ 'public-contracts',
254
+ 'contract-duplicate',
255
+ path,
256
+ `Duplicate public package contract ${identity}.`
257
+ );
258
+ }
259
+ identities.add(identity);
260
+ contracts.push({
261
+ registry,
262
+ name: typeof contract.name === 'string' ? contract.name : null,
263
+ version: typeof contract.version === 'string' ? contract.version : null,
264
+ role: typeof contract.role === 'string' ? contract.role : null,
265
+ });
266
+ });
267
+
268
+ for (const required of REQUIRED_CONTRACTS) {
269
+ if (
270
+ !contracts.some(
271
+ (contract) => contract.registry === required.registry && contract.role === required.role
272
+ )
273
+ ) {
274
+ issue(
275
+ errors,
276
+ 'public-contracts',
277
+ 'required-contract-missing',
278
+ 'contracts',
279
+ `Missing ${required.registry} contract for role ${required.role}.`
280
+ );
281
+ }
282
+ }
283
+ return contracts;
284
+ }
285
+
286
+ function checkBindings(config, errors) {
287
+ if (!isRecord(config.bindings)) {
288
+ issue(
289
+ errors,
290
+ 'caller-bindings',
291
+ 'bindings-missing',
292
+ 'bindings',
293
+ 'Caller-owned bindings are required.'
294
+ );
295
+ return [];
296
+ }
297
+
298
+ const bindings = [];
299
+ for (const id of REQUIRED_BINDINGS) {
300
+ if (!isRecord(config.bindings[id])) {
301
+ issue(
302
+ errors,
303
+ 'caller-bindings',
304
+ 'binding-missing',
305
+ `bindings.${id}`,
306
+ `Missing ${id} binding.`
307
+ );
308
+ }
309
+ }
310
+
311
+ for (const [id, binding] of Object.entries(config.bindings)) {
312
+ const path = `bindings.${id}`;
313
+ if (!validId(id)) {
314
+ issue(
315
+ errors,
316
+ 'caller-bindings',
317
+ 'binding-id-invalid',
318
+ path,
319
+ 'Binding id must be a portable identifier.'
320
+ );
321
+ }
322
+ if (!isRecord(binding)) {
323
+ issue(errors, 'caller-bindings', 'binding-invalid', path, 'Binding must be an object.');
324
+ continue;
325
+ }
326
+ checkKnownKeys(binding, new Set(['owner', 'env']), path, 'caller-bindings', errors);
327
+ if (binding.owner !== 'caller') {
328
+ issue(
329
+ errors,
330
+ 'caller-bindings',
331
+ 'binding-owner-invalid',
332
+ `${path}.owner`,
333
+ 'Binding ownership must remain with the caller.'
334
+ );
335
+ }
336
+ if (!validEnvName(binding.env)) {
337
+ issue(
338
+ errors,
339
+ 'caller-bindings',
340
+ 'binding-env-invalid',
341
+ `${path}.env`,
342
+ 'Binding must name an environment key without embedding its value.'
343
+ );
344
+ }
345
+ bindings.push({ id, owner: binding.owner, env: binding.env });
346
+ }
347
+ return bindings;
348
+ }
349
+
350
+ function checkOperations(config, errors) {
351
+ const operations = config.operations;
352
+ if (!isRecord(operations)) {
353
+ issue(
354
+ errors,
355
+ 'bounded-operations',
356
+ 'operations-missing',
357
+ 'operations',
358
+ 'Bounded autonomy and receipt policy are required.'
359
+ );
360
+ return;
361
+ }
362
+ checkKnownKeys(
363
+ operations,
364
+ new Set(['autonomy', 'receipts']),
365
+ 'operations',
366
+ 'bounded-operations',
367
+ errors
368
+ );
369
+
370
+ const autonomy = operations.autonomy;
371
+ if (!isRecord(autonomy)) {
372
+ issue(
373
+ errors,
374
+ 'bounded-operations',
375
+ 'autonomy-missing',
376
+ 'operations.autonomy',
377
+ 'Autonomy policy is required.'
378
+ );
379
+ } else {
380
+ checkKnownKeys(
381
+ autonomy,
382
+ new Set(['mode', 'maxAttempts', 'leaseSeconds', 'stopConditions']),
383
+ 'operations.autonomy',
384
+ 'bounded-operations',
385
+ errors
386
+ );
387
+ if (autonomy.mode !== 'bounded') {
388
+ issue(
389
+ errors,
390
+ 'bounded-operations',
391
+ 'autonomy-unbounded',
392
+ 'operations.autonomy.mode',
393
+ 'Autonomy mode must be bounded.'
394
+ );
395
+ }
396
+ if (!Number.isInteger(autonomy.maxAttempts) || autonomy.maxAttempts < 1) {
397
+ issue(
398
+ errors,
399
+ 'bounded-operations',
400
+ 'max-attempts-invalid',
401
+ 'operations.autonomy.maxAttempts',
402
+ 'maxAttempts must be a positive integer.'
403
+ );
404
+ }
405
+ if (!Number.isInteger(autonomy.leaseSeconds) || autonomy.leaseSeconds < 1) {
406
+ issue(
407
+ errors,
408
+ 'bounded-operations',
409
+ 'lease-invalid',
410
+ 'operations.autonomy.leaseSeconds',
411
+ 'leaseSeconds must be a positive integer.'
412
+ );
413
+ }
414
+ const stops = Array.isArray(autonomy.stopConditions) ? autonomy.stopConditions : [];
415
+ for (const stop of REQUIRED_STOP_CONDITIONS) {
416
+ if (!stops.includes(stop)) {
417
+ issue(
418
+ errors,
419
+ 'bounded-operations',
420
+ 'stop-condition-missing',
421
+ 'operations.autonomy.stopConditions',
422
+ `Missing stop condition ${stop}.`
423
+ );
424
+ }
425
+ }
426
+ }
427
+
428
+ const receipts = operations.receipts;
429
+ if (!isRecord(receipts)) {
430
+ issue(
431
+ errors,
432
+ 'bounded-operations',
433
+ 'receipts-missing',
434
+ 'operations.receipts',
435
+ 'Receipt policy is required.'
436
+ );
437
+ return;
438
+ }
439
+ checkKnownKeys(
440
+ receipts,
441
+ new Set(['directory', 'format', 'maxAgeSeconds']),
442
+ 'operations.receipts',
443
+ 'bounded-operations',
444
+ errors
445
+ );
446
+ if (!isPortablePath(receipts.directory)) {
447
+ issue(
448
+ errors,
449
+ 'portable-inputs',
450
+ 'receipt-path-not-portable',
451
+ 'operations.receipts.directory',
452
+ 'Receipt directory must be a relative path without parent traversal.'
453
+ );
454
+ }
455
+ if (receipts.format !== 'json') {
456
+ issue(
457
+ errors,
458
+ 'bounded-operations',
459
+ 'receipt-format-invalid',
460
+ 'operations.receipts.format',
461
+ 'Receipt format must be json.'
462
+ );
463
+ }
464
+ if (!Number.isInteger(receipts.maxAgeSeconds) || receipts.maxAgeSeconds < 1) {
465
+ issue(
466
+ errors,
467
+ 'bounded-operations',
468
+ 'receipt-freshness-invalid',
469
+ 'operations.receipts.maxAgeSeconds',
470
+ 'maxAgeSeconds must be a positive integer.'
471
+ );
472
+ }
473
+ }
474
+
475
+ export function inspectHoloSystemConfig(config) {
476
+ const errors = [];
477
+ const warnings = [];
478
+ if (!isRecord(config)) {
479
+ issue(errors, 'schema', 'config-invalid', '$', 'Configuration must be a JSON object.');
480
+ return {
481
+ schema: HOLOSYSTEM_INSPECTION_SCHEMA,
482
+ ready: false,
483
+ portable: false,
484
+ checks: [{ id: 'schema', ok: false, errorCount: 1 }],
485
+ errors,
486
+ warnings,
487
+ summary: { consumer: null, packages: [], bindings: [] },
488
+ };
489
+ }
490
+
491
+ checkSchema(config, errors);
492
+ checkConsumer(config, errors);
493
+ const packages = checkContracts(config, errors);
494
+ const bindings = checkBindings(config, errors);
495
+ checkOperations(config, errors);
496
+ inspectSensitiveValues(config, '', errors);
497
+ checkKnownKeys(config, KNOWN_TOP_LEVEL_KEYS, '', 'schema', errors);
498
+
499
+ const checkIds = [
500
+ 'schema',
501
+ 'consumer',
502
+ 'public-contracts',
503
+ 'caller-bindings',
504
+ 'portable-inputs',
505
+ 'bounded-operations',
506
+ ];
507
+ const checks = checkIds.map((id) => {
508
+ const errorCount = errors.filter((error) => error.check === id).length;
509
+ return { id, ok: errorCount === 0, errorCount };
510
+ });
511
+
512
+ return {
513
+ schema: HOLOSYSTEM_INSPECTION_SCHEMA,
514
+ ready: errors.length === 0,
515
+ portable: errors.every(
516
+ (error) =>
517
+ !['schema', 'consumer', 'public-contracts', 'caller-bindings', 'portable-inputs'].includes(
518
+ error.check
519
+ )
520
+ ),
521
+ checks,
522
+ errors,
523
+ warnings,
524
+ summary: {
525
+ consumer: {
526
+ id: typeof config.consumer?.id === 'string' ? config.consumer.id : null,
527
+ workspace:
528
+ typeof config.consumer?.workspace === 'string' ? config.consumer.workspace : null,
529
+ },
530
+ packages,
531
+ bindings,
532
+ },
533
+ };
534
+ }
535
+
536
+ export function createHoloSystemConfig({
537
+ consumerId = 'external-founder',
538
+ workspace = 'default',
539
+ contracts = DEFAULT_PUBLIC_PACKAGE_CONTRACTS,
540
+ bindings = DEFAULT_CALLER_BINDINGS,
541
+ receiptDirectory = DEFAULT_OPERATIONS.receipts.directory,
542
+ maxAttempts = DEFAULT_OPERATIONS.autonomy.maxAttempts,
543
+ leaseSeconds = DEFAULT_OPERATIONS.autonomy.leaseSeconds,
544
+ receiptMaxAgeSeconds = DEFAULT_OPERATIONS.receipts.maxAgeSeconds,
545
+ } = {}) {
546
+ const config = {
547
+ schema: HOLOSYSTEM_CONFIG_SCHEMA,
548
+ consumer: { id: consumerId, workspace },
549
+ contracts: clone(contracts),
550
+ bindings: clone(bindings),
551
+ operations: {
552
+ autonomy: {
553
+ mode: 'bounded',
554
+ maxAttempts,
555
+ leaseSeconds,
556
+ stopConditions: clone(REQUIRED_STOP_CONDITIONS),
557
+ },
558
+ receipts: {
559
+ directory: receiptDirectory,
560
+ format: 'json',
561
+ maxAgeSeconds: receiptMaxAgeSeconds,
562
+ },
563
+ },
564
+ };
565
+ const report = inspectHoloSystemConfig(config);
566
+ if (!report.ready) {
567
+ const error = new TypeError(
568
+ `Cannot create HoloSystem config: ${report.errors.map((item) => item.code).join(', ')}`
569
+ );
570
+ error.report = report;
571
+ throw error;
572
+ }
573
+ return config;
574
+ }