@josfox/jos 4.1.0 → 5.0.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.
Files changed (64) hide show
  1. package/LICENSE +208 -21
  2. package/PROMISES.md +52 -0
  3. package/README.md +334 -138
  4. package/SECURITY-MCP.md +117 -0
  5. package/SECURITY-SECRETS.md +108 -0
  6. package/STYLE.md +23 -0
  7. package/bin/jos +170 -160
  8. package/examples/01-hello.jos +44 -0
  9. package/examples/02-criteria.jos +54 -0
  10. package/examples/03-flow-gate.jos +83 -0
  11. package/examples/04-use-template/README.md +22 -0
  12. package/examples/04-use-template/lib/greeting.jos +56 -0
  13. package/examples/04-use-template/use.jos +64 -0
  14. package/examples/05-sealed/.jos/manifest.jos +53 -0
  15. package/examples/05-sealed/README.md +25 -0
  16. package/examples/06-requires.jos +42 -0
  17. package/examples/07-sandbox.jos +39 -0
  18. package/examples/08-ids-ledger-meter.md +26 -0
  19. package/examples/10-ollama.jos +47 -0
  20. package/examples/11-openai.jos +47 -0
  21. package/examples/12-gemini.jos +47 -0
  22. package/examples/13-anthropic.jos +47 -0
  23. package/examples/14-serve.jos +88 -0
  24. package/examples/README.md +105 -0
  25. package/examples/hello.jos +47 -0
  26. package/package.json +57 -40
  27. package/promises.json +894 -0
  28. package/src/args.js +47 -0
  29. package/src/commands/_frozen/add.README.md +1 -0
  30. package/src/commands/_frozen/get.README.md +1 -0
  31. package/src/commands/_frozen/llm.README.md +1 -0
  32. package/src/commands/_frozen/prompts.README.md +1 -0
  33. package/src/commands/_frozen/repo.README.md +1 -0
  34. package/src/commands/_frozen/serve-explorer.README.md +1 -0
  35. package/src/commands/ids.js +101 -0
  36. package/src/commands/init.js +128 -162
  37. package/src/commands/ledger.js +94 -0
  38. package/src/commands/meter.js +96 -0
  39. package/src/commands/migrate.js +214 -0
  40. package/src/commands/overview.js +118 -0
  41. package/src/commands/run.js +194 -244
  42. package/src/commands/runs.js +102 -0
  43. package/src/commands/secrets.js +717 -137
  44. package/src/commands/start.js +210 -0
  45. package/src/commands/validate.js +178 -194
  46. package/src/commands/verify.js +56 -98
  47. package/src/commands/version.js +23 -0
  48. package/src/intention.js +114 -0
  49. package/src/kernel.js +69 -0
  50. package/src/ledgers.js +57 -0
  51. package/src/mcp-guide.js +138 -0
  52. package/src/mcp.js +734 -0
  53. package/src/serve.js +48 -781
  54. package/src/ui.js +98 -0
  55. package/src/adapters/index.js +0 -16
  56. package/src/adapters/native.js +0 -41
  57. package/src/adapters/ollama.js +0 -140
  58. package/src/commands/add.js +0 -145
  59. package/src/commands/get.js +0 -245
  60. package/src/commands/llm.js +0 -159
  61. package/src/commands/prompts.js +0 -229
  62. package/src/commands/repo.js +0 -139
  63. package/src/utils/crypto.js +0 -41
  64. package/src/utils/request.js +0 -41
@@ -0,0 +1,210 @@
1
+ /**
2
+ * JOS START - quick start: writes a folder (jos-start/ or the one given) with four examples that run without keys and
3
+ * without network, and a README with the commands in order and what each prints:
4
+ * hello.jos an atom: intention, success criteria, guardrails, one shell step
5
+ * flow.jos a flow with a human gate (stops with code 1 until `--approve build`)
6
+ * sealed/ a folder whose .jos/manifest.jos is sealed with the directory hash jos-core computes (SPEC-2.0 §9.3)
7
+ * limits.jos guardrails that stop steps: an avoid pattern (code 3) and a time limit (code 124)
8
+ * Every document is validated by jos-core before anything is written. Nothing is overwritten: if one of the files
9
+ * exists, nothing is written. Exit codes: 0 written, 1 exists or cannot be written, 5 no kernel, 64 usage.
10
+ */
11
+ 'use strict';
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const kernel = require('../kernel');
15
+ const ui = require('../ui');
16
+ const { RULE } = ui;
17
+ const { parse, problem } = require('../args');
18
+
19
+ const DEFAULT_DIR = 'jos-start';
20
+ const USAGE = 'usage: jos start [dir]';
21
+
22
+ const HELP = (C) => `
23
+ ${C.purple}${RULE}${C.reset}
24
+ ${C.bold}JOS START${C.reset} // Four examples to run first
25
+ ${C.purple}${RULE}${C.reset}
26
+
27
+ ${C.bold}Usage:${C.reset}
28
+ jos start [dir] (default: ./${DEFAULT_DIR})
29
+
30
+ ${C.bold}Writes:${C.reset}
31
+ hello.jos an atom: intention, success criteria, guardrails, one shell step
32
+ flow.jos a flow with a human gate: code 1 until you run it with --approve build
33
+ sealed/ a folder sealed by its directory hash: edit .jos/manifest.jos and it is code 3
34
+ limits.jos guardrails at work: an avoid pattern blocks a step (code 3), a time limit
35
+ stops another (code 124)
36
+ README.md the commands in order and what each one prints
37
+
38
+ They run without keys and without network. Every document is validated by jos-core
39
+ before writing; nothing is overwritten (if one of the files exists, nothing is written).
40
+ `;
41
+
42
+ const LIMIT = { seconds: 10, output_bytes: 65536, retries: 0 };
43
+ const head = (kind, id, objective, criteria) => ({
44
+ jos: { version: '2.0.0', kind }, id, meta: { name: id, version: '1.0.0' },
45
+ intention: { objective, success_criteria: criteria },
46
+ });
47
+
48
+ function documents() {
49
+ const hello = Object.assign(head('atom', 'hello', 'Print a greeting and exit cleanly', [{ exit: 0 }, { stdout_contains: 'hello' }]), {
50
+ guardrails: { avoid: ['rm -rf', 'curl', 'wget'], limit: LIMIT },
51
+ execution: { engine: 'shell', run: ['echo', 'hello, .jos'], env: ['PATH'] },
52
+ });
53
+ const step = (word, objective) => ({ engine: 'shell', run: ['echo', word], intention: { objective, success_criteria: [{ stdout_contains: word }] } });
54
+ const flow = Object.assign(head('flow', 'flow', 'Build, wait for a person to approve the build, then publish', [{ exit: 0 }]), {
55
+ guardrails: { avoid: ['rm -rf'], limit: LIMIT },
56
+ orchestration: {
57
+ definitions: { build: step('built', 'Make the artifact'), publish: step('published', 'Publish the approved build') },
58
+ flows: { main: { steps: ['build', 'publish'], gates: { build: { kind: 'human' } } } },
59
+ },
60
+ });
61
+ const sealed = Object.assign(head('directory', 'sealed', 'Run only while the folder is exactly as it was sealed', [{ exit: 0 }, { stdout_contains: 'sealed folder ran' }]), {
62
+ guardrails: { limit: LIMIT },
63
+ orchestration: {
64
+ definitions: { say: { engine: 'shell', run: ['echo', 'the sealed folder ran'] } },
65
+ flows: { main: { steps: ['say'] } },
66
+ },
67
+ });
68
+ const limits = Object.assign(head('flow', 'limits', 'Show guardrails stopping steps: an avoid pattern before launch, a time limit during the run', [{ exit: 0 }]), {
69
+ guardrails: { avoid: ['rm -rf'], limit: LIMIT },
70
+ orchestration: {
71
+ definitions: {
72
+ cleanup: { engine: 'shell', run: ['echo', 'rm -rf ./build'], execution: { on_error: 'continue' }, description: 'Matches avoid: never starts (code 3)' },
73
+ slow: { engine: 'shell', run: ['sleep', '5'], guardrails: { limit: { seconds: 1 } }, description: 'Needs 5 s, may take 1: stopped (code 124)' },
74
+ },
75
+ flows: { main: { steps: ['cleanup', 'slow'] } },
76
+ },
77
+ });
78
+ return { hello, flow, sealed, limits };
79
+ }
80
+
81
+ const README = (dir, version, artifact) => `# jos start
82
+
83
+ Four \`.jos\` examples written by \`jos start\`. They run without keys and without network.
84
+ Run the commands in this order from \`${dir}\`; each \`jos run\` prints the intention first,
85
+ then the verdict of every criterion, then \`Intention met\` or \`Intention NOT met\`.
86
+
87
+ | Command | What you see | Exit |
88
+ |---|---|---|
89
+ | \`jos validate hello.jos\` | the intention, \`✓ VALIDATION PASSED\`; nothing runs | 0 |
90
+ | \`jos run hello.jos\` | \`hello, .jos\`, then \`✓ exit\`, \`✓ stdout_contains\`, \`✓ Intention met\` | 0 |
91
+ | \`jos run flow.jos\` | \`build\` runs, then the human gate stops the flow: \`Intention NOT met (code 1: not done)\` and the command to approve | 1 |
92
+ | \`jos run flow.jos --approve build\` | \`build\` and \`publish\` run: \`published\`, \`✓ Intention met\` | 0 |
93
+ | \`jos ids hello.jos flow.jos sealed\` | the \`art_\` (content), \`ent_\` (name) and, for \`sealed\`, the directory hash | 0 |
94
+ | \`jos run sealed\` | \`the sealed folder ran\`: the folder's hash matches \`integrity.hash\` | 0 |
95
+ | \`jos run limits.jos\` | \`cleanup\` blocked by \`avoid\` before it starts (code 3), \`slow\` stopped after 1 s (code 124) | 124 |
96
+ | \`jos runs\` | the runs above, newest first, with their codes | 0 |
97
+ | \`jos ledger verify <run_id>\` | \`✓ LEDGER VERIFIED\`: every hash of the run's ledger recomputed | 0 |
98
+ | \`jos meter <run_id>\` | steps, attempts, time; tokens and cost "not reported" (the shell engine reports none) | 0 |
99
+
100
+ To see the seal work, change the text \`the sealed folder ran\` in \`sealed/.jos/manifest.jos\`
101
+ and run \`jos run sealed\` again: \`Intention NOT met (code 3: blocked)\`, because the declared hash
102
+ no longer matches. Undo the change and it runs again.
103
+
104
+ The ledgers of these runs are in \`~/.jos/runs/\` (for \`sealed\`, in \`sealed/.jos/replays/\`).
105
+
106
+ ## What \`jos run hello.jos\` prints (NO_COLOR)
107
+
108
+ \`\`\`
109
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
110
+ JOS RUN // jos-core ${version}
111
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
112
+
113
+ File: hello.jos
114
+
115
+ Intention: Print a greeting and exit cleanly
116
+ Done when:
117
+ • exit 0
118
+ • stdout contains "hello"
119
+ Avoid: "rm -rf", "curl", "wget"
120
+ Limit: 10 s, 65536 output bytes per stream, 0 retries
121
+
122
+ ID: ${artifact}
123
+ Input version: 2.0.0
124
+ Capability: cap_… (local-trusted)
125
+ Ledger: ~/.jos/runs/run_…/ledger.jsonl
126
+
127
+ hello, .jos
128
+
129
+ Verdict:
130
+ [1] hello ✓ code 0
131
+ ✓ exit: exit 0 (want 0)
132
+ ✓ stdout_contains: found
133
+
134
+ ✓ Intention met (code 0, run_…)
135
+ \`\`\`
136
+
137
+ The \`art_\` is the hash of the document's content: the same on every machine, in both reference kernels.
138
+ `;
139
+
140
+ exports.execute = async (args) => {
141
+ const err = ui.printer(process.stderr);
142
+ const { files, help, error } = parse(args, {});
143
+ if (help) {
144
+ const out = ui.printer(process.stdout);
145
+ out.text(HELP(out.C));
146
+ return 0;
147
+ }
148
+ if (error || files.length > 1) {
149
+ err.fail('jos start: ' + (error ? problem(error) : 'takes at most one folder'));
150
+ err.hint(USAGE);
151
+ return 64;
152
+ }
153
+ const given = files.length ? files[0] : DEFAULT_DIR;
154
+ const dir = path.resolve(given);
155
+
156
+ let k;
157
+ let core;
158
+ try { k = kernel.locate(); core = kernel.need(k, ['validate', 'dirHash']); } catch (e) { return kernel.fail(e); }
159
+ const docs = documents();
160
+ for (const [name, doc] of Object.entries(docs)) {
161
+ const v = core.validate(doc);
162
+ if (!v.valid) throw new Error('jos start: the ' + name + ' example is not valid for jos-core ' + k.version + ': ' + v.errors.join('; '));
163
+ }
164
+ if (fs.existsSync(dir) && !fs.statSync(dir).isDirectory()) {
165
+ err.fail('jos start: ' + given + ' exists and is not a folder');
166
+ return 1;
167
+ }
168
+ const taken = ['hello.jos', 'flow.jos', 'limits.jos', 'sealed', 'README.md'].filter((n) => fs.existsSync(path.join(dir, n)));
169
+ if (taken.length) {
170
+ err.fail('jos start: ' + given + ' already has ' + taken.join(', '), '(nothing was written; nothing is overwritten)');
171
+ err.hint('choose another folder: jos start <dir>');
172
+ return 1;
173
+ }
174
+
175
+ const shown = ui.shellArg(given);
176
+ try {
177
+ fs.mkdirSync(path.join(dir, 'sealed', '.jos'), { recursive: true });
178
+ const write = (name, text) => fs.writeFileSync(path.join(dir, name), text, { flag: 'wx' });
179
+ for (const name of ['hello', 'flow', 'limits']) write(name + '.jos', JSON.stringify(docs[name], null, 2) + '\n');
180
+ // seal: write the manifest, hash the folder with the kernel (integrity.hash itself is not hashed), write it again
181
+ const manifest = path.join(dir, 'sealed', '.jos', 'manifest.jos');
182
+ write('sealed/.jos/manifest.jos', JSON.stringify(docs.sealed, null, 2) + '\n');
183
+ docs.sealed.integrity = { hash: core.dirHash(path.dirname(manifest)).hash };
184
+ fs.writeFileSync(manifest, JSON.stringify(docs.sealed, null, 2) + '\n');
185
+ write('README.md', README(given, k.version, core.validate(docs.hello).artifact));
186
+ } catch (e) {
187
+ err.fail('jos start: cannot write in ' + given + ': ' + (e && e.code ? e.code : e.message));
188
+ return 1;
189
+ }
190
+
191
+ err.header('JOS START', 'jos-core ' + k.version);
192
+ err.ok('Created ' + given + '/', '(four examples; no keys, no network)');
193
+ err.item('hello.jos an atom: intention, criteria, guardrails');
194
+ err.item('flow.jos a flow with a human gate');
195
+ err.item('sealed/ a folder sealed by its hash: ' + docs.sealed.integrity.hash.slice(0, 19) + '…');
196
+ err.item('limits.jos guardrails at work: avoid (code 3) and limit (code 124)');
197
+ err.item('README.md the commands in order and what each prints');
198
+ err.line();
199
+ err.line(`${err.C.bold}Next:${err.C.reset}`);
200
+ err.cmd('cd ' + shown);
201
+ err.cmd('jos validate hello.jos');
202
+ err.cmd('jos run hello.jos');
203
+ err.cmd('jos run flow.jos # stops at the gate: code 1');
204
+ err.cmd('jos run flow.jos --approve build');
205
+ err.cmd('jos run sealed');
206
+ err.cmd('jos run limits.jos # code 124: the receipts show 3 and 124');
207
+ err.cmd('jos runs');
208
+ err.line();
209
+ return 0;
210
+ };
@@ -1,194 +1,178 @@
1
- /**
2
- * JOS VALIDATE Command - Validate .jos artifacts
3
- * Checks schema, required keys, and integrity
4
- * Format version v0.0.7 — Specification maturity v0.1.0 (Alpha)
5
- */
6
-
7
- const fs = require('fs');
8
- const path = require('path');
9
- const crypto = require('crypto');
10
-
11
- const C = {
12
- reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
13
- purple: '\x1b[38;5;135m', cyan: '\x1b[38;5;51m',
14
- green: '\x1b[38;5;78m', red: '\x1b[38;5;196m',
15
- yellow: '\x1b[38;5;220m', gray: '\x1b[38;5;245m'
16
- };
17
-
18
- // Witness Logging
19
- function logWitness(home, eventType, data) {
20
- const runId = global.JOS_VALIDATE_ID || new Date().toISOString().replace(/[:.]/g, '-');
21
- global.JOS_VALIDATE_ID = runId;
22
-
23
- const runDir = path.join(home, 'runs', runId);
24
- if (!fs.existsSync(runDir)) fs.mkdirSync(runDir, { recursive: true });
25
-
26
- const event = {
27
- timestamp: new Date().toISOString(),
28
- type: `validate:${eventType}`,
29
- ...data
30
- };
31
-
32
- fs.appendFileSync(path.join(runDir, 'events.jsonl'), JSON.stringify(event) + '\n');
33
- }
34
-
35
- // Required keys for JOSFOXAI + MAGIC compliance (ALL REQUIRED - NO EXCEPTIONS)
36
- const JOSFOXAI_KEYS = ['jos', 'orchestration_contract', 'security', 'files', 'orchestration', 'x_run_params', 'adaptive_ai', 'id_jos'];
37
- const MAGIC_KEYS = ['meta', 'artifacts', 'guardrails', 'intention', 'capabilities'];
38
- const ALL_REQUIRED_KEYS = [...JOSFOXAI_KEYS, ...MAGIC_KEYS];
39
-
40
- exports.execute = async (args, home) => {
41
- const target = args[0];
42
- const insecure = args.includes('--insecure');
43
- const verbose = args.includes('--verbose') || args.includes('-v');
44
-
45
- if (!target || args.includes('--help')) {
46
- console.log(`
47
- ${C.purple}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}
48
- ${C.bold}JOS VALIDATE${C.reset} // Validate .jos artifacts (STRICT)
49
- ${C.purple}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}
50
-
51
- ${C.bold}Usage:${C.reset}
52
- jos validate <file.jos> [options]
53
-
54
- ${C.bold}Options:${C.reset}
55
- --insecure Skip integrity verification
56
- --verbose Show detailed validation information
57
-
58
- ${C.bold}ALL REQUIRED Keys:${C.reset}
59
- JOSFOXAI: ${JOSFOXAI_KEYS.join(', ')}
60
- MAGIC: ${MAGIC_KEYS.join(', ')}
61
-
62
- ${C.bold}Validates:${C.reset}
63
- 1. JSON syntax
64
- 2. ALL JOSFOXAI keys (8 required)
65
- 3. ALL MAGIC keys (5 required)
66
- 4. Integrity manifest (unless --insecure)
67
- `);
68
- return;
69
- }
70
-
71
- console.log(`\n${C.purple}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}`);
72
- console.log(`${C.bold}JOS VALIDATE${C.reset} // ${C.gray}Format v0.0.7 — STRICT MODE${C.reset}`);
73
- console.log(`${C.purple}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}`);
74
- console.log(`\n${C.cyan}📋 Artifact:${C.reset} ${target}`);
75
-
76
- logWitness(home, 'start', { artifact: target });
77
-
78
- let allPassed = true;
79
- const results = [];
80
-
81
- // 1. JSON Syntax Check
82
- let content, artifact;
83
- try {
84
- content = fs.readFileSync(target, 'utf8');
85
- artifact = JSON.parse(content);
86
- console.log(`${C.green} ✓ JSON Syntax Valid${C.reset}`);
87
- results.push({ check: 'json_syntax', passed: true });
88
- logWitness(home, 'check_passed', { check: 'json_syntax' });
89
- } catch (e) {
90
- console.log(`${C.red} ✖ JSON Error: ${e.message}${C.reset}`);
91
- results.push({ check: 'json_syntax', passed: false, error: e.message });
92
- logWitness(home, 'check_failed', { check: 'json_syntax', error: e.message });
93
- process.exit(1);
94
- }
95
-
96
- // 2. JOSFOXAI Keys Check (ALL 8 REQUIRED)
97
- const missingJosfoxai = JOSFOXAI_KEYS.filter(k => !artifact[k]);
98
- if (missingJosfoxai.length > 0) {
99
- console.log(`${C.red} ✖ Missing JOSFOXAI Keys: ${missingJosfoxai.join(', ')}${C.reset}`);
100
- results.push({ check: 'josfoxai_keys', passed: false, missing: missingJosfoxai });
101
- logWitness(home, 'check_failed', { check: 'josfoxai_keys', missing: missingJosfoxai });
102
- allPassed = false;
103
- } else {
104
- console.log(`${C.green} ✓ JOSFOXAI Keys Present (${JOSFOXAI_KEYS.length}/${JOSFOXAI_KEYS.length})${C.reset}`);
105
- results.push({ check: 'josfoxai_keys', passed: true });
106
- logWitness(home, 'check_passed', { check: 'josfoxai_keys' });
107
- }
108
-
109
- // 3. MAGIC Keys Check (ALL 5 REQUIRED)
110
- const missingMagic = MAGIC_KEYS.filter(k => !artifact[k]);
111
- if (missingMagic.length > 0) {
112
- console.log(`${C.red} ✖ Missing MAGIC Keys: ${missingMagic.join(', ')}${C.reset}`);
113
- results.push({ check: 'magic_keys', passed: false, missing: missingMagic });
114
- logWitness(home, 'check_failed', { check: 'magic_keys', missing: missingMagic });
115
- allPassed = false;
116
- } else {
117
- console.log(`${C.green} ✓ MAGIC Keys Present (${MAGIC_KEYS.length}/${MAGIC_KEYS.length})${C.reset}`);
118
- results.push({ check: 'magic_keys', passed: true });
119
- logWitness(home, 'check_passed', { check: 'magic_keys' });
120
- }
121
-
122
- // 4. Orchestration Structure Check
123
- if (artifact.orchestration) {
124
- const hasDefinitions = artifact.orchestration.definitions && Object.keys(artifact.orchestration.definitions).length > 0;
125
- const hasFlows = artifact.orchestration.flows && Object.keys(artifact.orchestration.flows).length > 0;
126
-
127
- if (hasDefinitions && hasFlows) {
128
- const defCount = Object.keys(artifact.orchestration.definitions).length;
129
- const flowCount = Object.keys(artifact.orchestration.flows).length;
130
- console.log(`${C.green} ✓ Orchestration Structure (${defCount} definitions, ${flowCount} flows)${C.reset}`);
131
- results.push({ check: 'orchestration_structure', passed: true, definitions: defCount, flows: flowCount });
132
- } else {
133
- console.log(`${C.yellow} ⚠ Orchestration may be incomplete${C.reset}`);
134
- results.push({ check: 'orchestration_structure', passed: true, warning: 'may be incomplete' });
135
- }
136
- }
137
-
138
- // 5. Integrity Check
139
- if (!insecure) {
140
- if (artifact.security?.integrity_ref) {
141
- const manifestPath = path.resolve(path.dirname(target), artifact.security.integrity_ref);
142
- if (fs.existsSync(manifestPath)) {
143
- try {
144
- const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
145
- const hash = crypto.createHash('sha256').update(content).digest('hex');
146
- if (hash === manifest.artifact_sha256) {
147
- console.log(`${C.green} ✓ Integrity Verified (SHA-256 match)${C.reset}`);
148
- if (verbose) {
149
- console.log(`${C.gray} Hash: ${hash}${C.reset}`);
150
- }
151
- results.push({ check: 'integrity', passed: true, hash: hash });
152
- logWitness(home, 'integrity_verified', { hash: hash, manifest: artifact.security.integrity_ref });
153
- } else {
154
- console.log(`${C.red} ✖ Integrity Mismatch!${C.reset}`);
155
- if (verbose) {
156
- console.log(`${C.gray} Expected: ${manifest.artifact_sha256}${C.reset}`);
157
- console.log(`${C.gray} Actual: ${hash}${C.reset}`);
158
- }
159
- results.push({ check: 'integrity', passed: false, expected: manifest.artifact_sha256, actual: hash });
160
- logWitness(home, 'integrity_failed', { expected: manifest.artifact_sha256, actual: hash });
161
- allPassed = false;
162
- }
163
- } catch (e) {
164
- console.log(`${C.red} ✖ Invalid Manifest: ${e.message}${C.reset}`);
165
- results.push({ check: 'integrity', passed: false, error: e.message });
166
- allPassed = false;
167
- }
168
- } else {
169
- console.log(`${C.red} ✖ Manifest Not Found: ${artifact.security.integrity_ref}${C.reset}`);
170
- results.push({ check: 'integrity', passed: false, error: 'manifest not found' });
171
- logWitness(home, 'integrity_failed', { error: 'manifest not found' });
172
- allPassed = false;
173
- }
174
- } else {
175
- console.log(`${C.yellow} ⚠ No Integrity Reference (security.integrity_ref)${C.reset}`);
176
- results.push({ check: 'integrity', passed: true, warning: 'no integrity_ref' });
177
- }
178
- } else {
179
- console.log(`${C.yellow} ⚠ Integrity Check Skipped (--insecure)${C.reset}`);
180
- results.push({ check: 'integrity', skipped: true });
181
- }
182
-
183
- // Summary
184
- console.log('');
185
- if (allPassed) {
186
- console.log(`${C.green}${C.bold}✓ VALIDATION PASSED${C.reset}`);
187
- logWitness(home, 'success', { results: results });
188
- } else {
189
- console.log(`${C.red}${C.bold}✖ VALIDATION FAILED${C.reset}`);
190
- logWitness(home, 'failed', { results: results });
191
- process.exit(1);
192
- }
193
- console.log('');
194
- };
1
+ /**
2
+ * JOS VALIDATE - Validate a .jos document with the jos-core kernel
3
+ * Accepts 2.0.0, 1.1.0, 1.0.0 and 0.0.7 documents. Exit 0 valid, 2 invalid (also a valid document that --strict
4
+ * refuses: no objective, success criteria or guardrails written); 5 no kernel; 64 usage.
5
+ * Shows what jos-core infers (its `inferred` section, jos-core 2.0.0-rc.8): members the document does not write and
6
+ * the kernel applies, with their source, and what the document does not declare.
7
+ */
8
+ 'use strict';
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const kernel = require('../kernel');
12
+ const ui = require('../ui');
13
+ const { RULE } = ui;
14
+ const { split } = require('../args');
15
+ const intention = require('../intention');
16
+
17
+ // What --strict requires the document itself to write: an entry of `inferred` at one of these pointers means it does not.
18
+ const STRICT = ['/intention/objective', '/intention/success_criteria', '/guardrails'];
19
+ const OPTIONS = ['--json', '--strict'];
20
+
21
+ const HELP = (C) => `
22
+ ${C.purple}${RULE}${C.reset}
23
+ ${C.bold}JOS VALIDATE${C.reset} // Validate .jos documents with jos-core
24
+ ${C.purple}${RULE}${C.reset}
25
+
26
+ ${C.bold}Usage:${C.reset}
27
+ jos validate <file.jos> [--json] [--strict]
28
+ jos validate <folder> [--json] [--strict] (D or D/.jos: the directory D)
29
+ jos validate [--json] [--strict] -- <file.jos> (-- ends the options)
30
+
31
+ ${C.bold}Options:${C.reset}
32
+ --json Print the validation result as JSON, with jos-core's "inferred" section
33
+ --strict Also fail (exit 2) when the document does not write intention.objective,
34
+ intention.success_criteria and guardrails itself
35
+
36
+ ${C.bold}Checks:${C.reset}
37
+ UTF-8 and JSON syntax, forbidden keys, form detection (2.0.0, 1.1.0, 1.0.0, 0.0.7),
38
+ normalization to 2.0, schema, artifact id (art_<sha256>). Nothing runs.
39
+ A valid document's intention is shown as written: objective, success criteria
40
+ ("Done when"), avoid patterns and limits. "Inferred" lists what jos-core applies
41
+ that the document does not write (pointer, value, source: SPEC section or jos-core
42
+ default) and flags what it does not declare. The document alone is read: the
43
+ .jos/ chain of its folder is applied by jos run, not shown here.
44
+ A folder D (or D/.jos) is validated as a directory by jos-core (SPEC-2.0 9.1):
45
+ D/.jos/manifest.jos MUST be of kind directory (a 1.0.0 flow with
46
+ meta.labels.dotjos is accepted with a warning), every .jos under .jos/ is
47
+ validated, and the .jos/ chain above D may only restrict. jos run D refuses
48
+ the same manifests; --strict applies to the manifest.
49
+ `;
50
+
51
+ function isDirectory(p) {
52
+ try { return fs.statSync(p).isDirectory(); } catch (e) { return false; }
53
+ }
54
+
55
+ // A folder D (or D/.jos) is validated as the directory D by the kernel's validateDir, as `jos-core validate D` does:
56
+ // its four layers, and a manifest that is not of kind directory is code 2 at /jos/kind, the answer of `jos run D`
57
+ // (SPEC-2.0 §9.1, the 1.0.0 flow with meta.labels.dotjos accepted with a warning; readiness rc.7 pass 5 F-19).
58
+ // --strict applies to the manifest, the document of the folder.
59
+ function folder(out, err, k, given, dir, flags) {
60
+ let core;
61
+ try { core = kernel.need(k, ['validateDir', 'documentPath']); } catch (e) { return kernel.fail(e); }
62
+ const v = core.validateDir(dir, {});
63
+ const missing = flags.includes('--strict') && v.valid
64
+ ? STRICT.filter((p) => Object.prototype.hasOwnProperty.call(core.inferred(core.loadFile(core.documentPath(dir))), p)) : [];
65
+ const strictFail = () => {
66
+ err.fail('jos validate: --strict: the manifest of ' + given + ' does not declare ' + missing.map((p) => p.slice(1).replace('/', '.')).join(', '));
67
+ err.hint('write intention.objective, intention.success_criteria and guardrails in .jos/manifest.jos');
68
+ return 2;
69
+ };
70
+ if (flags.includes('--json')) {
71
+ ui.json(process.stdout, v);
72
+ if (missing.length) return strictFail();
73
+ return v.valid ? 0 : 2;
74
+ }
75
+ const { C } = out;
76
+ out.header('JOS VALIDATE', 'jos-core ' + k.version);
77
+ out.field('Folder', given);
78
+ for (const w of v.warnings) out.warn(w);
79
+ if (v.valid) {
80
+ out.ok('Valid directory', `(${v.files} documents, integrity ${v.integrity})`);
81
+ out.line();
82
+ if (missing.length) return strictFail();
83
+ out.ok(`${C.bold}VALIDATION PASSED`);
84
+ out.line();
85
+ return 0;
86
+ }
87
+ for (const e of v.errors) out.fail(e);
88
+ out.line();
89
+ out.fail(`${C.bold}VALIDATION FAILED`);
90
+ out.line();
91
+ return 2;
92
+ }
93
+
94
+ exports.execute = async (args) => {
95
+ const out = ui.printer(process.stdout);
96
+ const err = ui.printer(process.stderr);
97
+ const { C } = out;
98
+ const { flags, files, help } = split(args);
99
+ if (help) {
100
+ out.text(HELP(C));
101
+ return 0;
102
+ }
103
+ for (const f of flags) {
104
+ if (!OPTIONS.includes(f)) {
105
+ err.fail('jos validate: unknown option ' + f);
106
+ if (f === '--insecure' || f === '--verbose') err.hint('(removed in 5.0.0)');
107
+ return 64;
108
+ }
109
+ }
110
+ if (files.length !== 1) {
111
+ err.fail('jos validate: takes exactly one file');
112
+ err.hint('usage: jos validate <file.jos> [--json] [--strict]');
113
+ return 64;
114
+ }
115
+
116
+ let k;
117
+ let core;
118
+ try { k = kernel.locate(); core = kernel.need(k, ['inferred']); } catch (e) { return kernel.fail(e); }
119
+ const file = path.resolve(files[0]);
120
+ if (isDirectory(file)) return folder(out, err, k, files[0], file, flags);
121
+
122
+ let v;
123
+ let raw = null;
124
+ try {
125
+ raw = core.loadFile(file);
126
+ v = core.validate(raw);
127
+ } catch (e) {
128
+ if (!(e instanceof core.JosError)) throw e;
129
+ v = { valid: false, form: null, input_version: null, artifact: null, errors: e.errors, document: null };
130
+ }
131
+ // as `jos-core validate --json`: the section of the document alone, null when it is invalid
132
+ const inferred = v.valid ? core.inferred(raw) : null;
133
+ const missing = flags.includes('--strict') && inferred ? STRICT.filter((p) => Object.prototype.hasOwnProperty.call(inferred, p)) : [];
134
+ const strictFail = () => {
135
+ err.fail('jos validate: --strict: ' + files[0] + ' does not declare ' + missing.map((p) => p.slice(1).replace('/', '.')).join(', '));
136
+ err.hint('write intention.objective, intention.success_criteria and guardrails in the document');
137
+ return 2;
138
+ };
139
+
140
+ if (flags.includes('--json')) {
141
+ ui.json(process.stdout, Object.assign({}, v, { inferred }));
142
+ if (missing.length) return strictFail();
143
+ return v.valid ? 0 : 2;
144
+ }
145
+
146
+ out.header('JOS VALIDATE', 'jos-core ' + k.version);
147
+ out.field('Artifact', files[0]);
148
+ if (v.valid) {
149
+ out.ok('Valid', `(input ${v.form} ${v.input_version})`);
150
+ out.field('ID', v.artifact);
151
+ if (v.form === '0.0.7') {
152
+ out.warn('0.0.7 document, normalized to 2.0 in memory; the file is not rewritten');
153
+ // jos-core validate does not read the manifest (SPEC-2.0 §2.4, decision D4); jos run verifies it, --dry-run too
154
+ const ref = Object(raw.security).integrity_ref;
155
+ if (![undefined, null, ''].includes(ref)) {
156
+ out.warn('integrity_ref present: verified on run');
157
+ out.hint('validate does not read the manifest ' + JSON.stringify(ref) + '; jos run checks it before anything starts (also --dry-run), a mismatch is code 3');
158
+ out.hint('jos migrate ' + ui.shellArg(files[0]) + ' reseals it as a 2.0 document (integrity.hash)');
159
+ }
160
+ }
161
+ intention.intention(out, v.document);
162
+ intention.inferred(out, inferred);
163
+ out.line();
164
+ if (missing.length) {
165
+ out.fail(`${C.bold}VALIDATION FAILED`, '(--strict)');
166
+ out.line();
167
+ return strictFail();
168
+ }
169
+ out.ok(`${C.bold}VALIDATION PASSED`);
170
+ out.line();
171
+ return 0;
172
+ }
173
+ for (const e of v.errors) out.fail(e);
174
+ out.line();
175
+ out.fail(`${C.bold}VALIDATION FAILED`);
176
+ out.line();
177
+ return 2;
178
+ };