@josfox/jos 4.0.8 → 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.
- package/LICENSE +208 -21
- package/PROMISES.md +52 -0
- package/README.md +294 -146
- package/SECURITY-MCP.md +117 -0
- package/SECURITY-SECRETS.md +108 -0
- package/STYLE.md +23 -0
- package/bin/jos +139 -67
- package/bin/jos.cmd +5 -0
- package/examples/01-hello.jos +44 -0
- package/examples/02-criteria.jos +54 -0
- package/examples/03-flow-gate.jos +83 -0
- package/examples/04-use-template/README.md +22 -0
- package/examples/04-use-template/lib/greeting.jos +56 -0
- package/examples/04-use-template/use.jos +64 -0
- package/examples/05-sealed/.jos/manifest.jos +53 -0
- package/examples/05-sealed/README.md +25 -0
- package/examples/06-requires.jos +42 -0
- package/examples/07-sandbox.jos +39 -0
- package/examples/08-ids-ledger-meter.md +26 -0
- package/examples/10-ollama.jos +47 -0
- package/examples/11-openai.jos +47 -0
- package/examples/12-gemini.jos +47 -0
- package/examples/13-anthropic.jos +47 -0
- package/examples/14-serve.jos +88 -0
- package/examples/README.md +105 -0
- package/examples/hello.jos +47 -0
- package/package.json +31 -9
- package/promises.json +894 -0
- package/src/args.js +47 -0
- package/src/commands/_frozen/add.README.md +1 -0
- package/src/commands/_frozen/get.README.md +1 -0
- package/src/commands/_frozen/llm.README.md +1 -0
- package/src/commands/_frozen/prompts.README.md +1 -0
- package/src/commands/_frozen/repo.README.md +1 -0
- package/src/commands/_frozen/serve-explorer.README.md +1 -0
- package/src/commands/ids.js +101 -0
- package/src/commands/init.js +113 -147
- package/src/commands/ledger.js +94 -0
- package/src/commands/meter.js +96 -0
- package/src/commands/migrate.js +214 -0
- package/src/commands/overview.js +118 -0
- package/src/commands/run.js +164 -214
- package/src/commands/runs.js +102 -0
- package/src/commands/secrets.js +695 -115
- package/src/commands/start.js +210 -0
- package/src/commands/validate.js +153 -165
- package/src/commands/verify.js +56 -0
- package/src/commands/version.js +23 -0
- package/src/intention.js +114 -0
- package/src/kernel.js +69 -0
- package/src/ledgers.js +57 -0
- package/src/mcp-guide.js +138 -0
- package/src/mcp.js +734 -0
- package/src/serve.js +38 -770
- package/src/ui.js +98 -0
- package/src/commands/add.js +0 -145
- package/src/commands/get.js +0 -245
- package/src/commands/repo.js +0 -139
- package/src/index.js +0 -0
|
@@ -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
|
+
};
|
package/src/commands/validate.js
CHANGED
|
@@ -1,190 +1,178 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* JOS VALIDATE
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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.
|
|
5
7
|
*/
|
|
6
|
-
|
|
8
|
+
'use strict';
|
|
7
9
|
const fs = require('fs');
|
|
8
10
|
const path = require('path');
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
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;
|
|
11
|
+
const kernel = require('../kernel');
|
|
12
|
+
const ui = require('../ui');
|
|
13
|
+
const { RULE } = ui;
|
|
14
|
+
const { split } = require('../args');
|
|
15
|
+
const intention = require('../intention');
|
|
22
16
|
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
}
|
|
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'];
|
|
34
20
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
|
49
|
-
${C.purple}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}
|
|
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}
|
|
50
25
|
|
|
51
26
|
${C.bold}Usage:${C.reset}
|
|
52
|
-
jos validate <file.jos> [
|
|
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)
|
|
53
30
|
|
|
54
31
|
${C.bold}Options:${C.reset}
|
|
55
|
-
--
|
|
56
|
-
--
|
|
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
|
+
}
|
|
57
54
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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;
|
|
65
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
|
+
}
|
|
66
93
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
+
}
|
|
73
115
|
|
|
74
|
-
let
|
|
75
|
-
|
|
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);
|
|
76
121
|
|
|
77
|
-
|
|
78
|
-
let
|
|
122
|
+
let v;
|
|
123
|
+
let raw = null;
|
|
79
124
|
try {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
console.log(`${C.green} ✓ JSON Syntax Valid${C.reset}`);
|
|
83
|
-
results.push({ check: 'json_syntax', passed: true });
|
|
84
|
-
logWitness(home, 'check_passed', { check: 'json_syntax' });
|
|
125
|
+
raw = core.loadFile(file);
|
|
126
|
+
v = core.validate(raw);
|
|
85
127
|
} catch (e) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
logWitness(home, 'check_failed', { check: 'json_syntax', error: e.message });
|
|
89
|
-
process.exit(1);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// 2. JOSFOXAI Keys Check
|
|
93
|
-
const missingJosfoxai = JOSFOXAI_KEYS.filter(k => !artifact[k]);
|
|
94
|
-
if (missingJosfoxai.length > 0) {
|
|
95
|
-
console.log(`${C.red} ✖ Missing JOSFOXAI Keys: ${missingJosfoxai.join(', ')}${C.reset}`);
|
|
96
|
-
results.push({ check: 'josfoxai_keys', passed: false, missing: missingJosfoxai });
|
|
97
|
-
logWitness(home, 'check_failed', { check: 'josfoxai_keys', missing: missingJosfoxai });
|
|
98
|
-
allPassed = false;
|
|
99
|
-
} else {
|
|
100
|
-
console.log(`${C.green} ✓ JOSFOXAI Keys Present (${JOSFOXAI_KEYS.length}/${JOSFOXAI_KEYS.length})${C.reset}`);
|
|
101
|
-
results.push({ check: 'josfoxai_keys', passed: true });
|
|
102
|
-
logWitness(home, 'check_passed', { check: 'josfoxai_keys' });
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// 3. MAGIC Keys Check
|
|
106
|
-
const missingMagic = MAGIC_KEYS.filter(k => !artifact[k]);
|
|
107
|
-
if (missingMagic.length > 0) {
|
|
108
|
-
console.log(`${C.red} ✖ Missing MAGIC Keys: ${missingMagic.join(', ')}${C.reset}`);
|
|
109
|
-
results.push({ check: 'magic_keys', passed: false, missing: missingMagic });
|
|
110
|
-
logWitness(home, 'check_failed', { check: 'magic_keys', missing: missingMagic });
|
|
111
|
-
allPassed = false;
|
|
112
|
-
} else {
|
|
113
|
-
console.log(`${C.green} ✓ MAGIC Keys Present (${MAGIC_KEYS.length}/${MAGIC_KEYS.length})${C.reset}`);
|
|
114
|
-
results.push({ check: 'magic_keys', passed: true });
|
|
115
|
-
logWitness(home, 'check_passed', { check: 'magic_keys' });
|
|
128
|
+
if (!(e instanceof core.JosError)) throw e;
|
|
129
|
+
v = { valid: false, form: null, input_version: null, artifact: null, errors: e.errors, document: null };
|
|
116
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
|
+
};
|
|
117
139
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (hasDefinitions && hasFlows) {
|
|
124
|
-
const defCount = Object.keys(artifact.orchestration.definitions).length;
|
|
125
|
-
const flowCount = Object.keys(artifact.orchestration.flows).length;
|
|
126
|
-
console.log(`${C.green} ✓ Orchestration Structure (${defCount} definitions, ${flowCount} flows)${C.reset}`);
|
|
127
|
-
results.push({ check: 'orchestration_structure', passed: true, definitions: defCount, flows: flowCount });
|
|
128
|
-
} else {
|
|
129
|
-
console.log(`${C.yellow} ⚠ Orchestration may be incomplete${C.reset}`);
|
|
130
|
-
results.push({ check: 'orchestration_structure', passed: true, warning: 'may be incomplete' });
|
|
131
|
-
}
|
|
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;
|
|
132
144
|
}
|
|
133
145
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
results.push({ check: 'integrity', passed: true, hash: hash });
|
|
148
|
-
logWitness(home, 'integrity_verified', { hash: hash, manifest: artifact.security.integrity_ref });
|
|
149
|
-
} else {
|
|
150
|
-
console.log(`${C.red} ✖ Integrity Mismatch!${C.reset}`);
|
|
151
|
-
if (verbose) {
|
|
152
|
-
console.log(`${C.gray} Expected: ${manifest.artifact_sha256}${C.reset}`);
|
|
153
|
-
console.log(`${C.gray} Actual: ${hash}${C.reset}`);
|
|
154
|
-
}
|
|
155
|
-
results.push({ check: 'integrity', passed: false, expected: manifest.artifact_sha256, actual: hash });
|
|
156
|
-
logWitness(home, 'integrity_failed', { expected: manifest.artifact_sha256, actual: hash });
|
|
157
|
-
allPassed = false;
|
|
158
|
-
}
|
|
159
|
-
} catch (e) {
|
|
160
|
-
console.log(`${C.red} ✖ Invalid Manifest: ${e.message}${C.reset}`);
|
|
161
|
-
results.push({ check: 'integrity', passed: false, error: e.message });
|
|
162
|
-
allPassed = false;
|
|
163
|
-
}
|
|
164
|
-
} else {
|
|
165
|
-
console.log(`${C.red} ✖ Manifest Not Found: ${artifact.security.integrity_ref}${C.reset}`);
|
|
166
|
-
results.push({ check: 'integrity', passed: false, error: 'manifest not found' });
|
|
167
|
-
logWitness(home, 'integrity_failed', { error: 'manifest not found' });
|
|
168
|
-
allPassed = false;
|
|
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)');
|
|
169
159
|
}
|
|
170
|
-
} else {
|
|
171
|
-
console.log(`${C.yellow} ⚠ No Integrity Reference (security.integrity_ref)${C.reset}`);
|
|
172
|
-
results.push({ check: 'integrity', passed: true, warning: 'no integrity_ref' });
|
|
173
160
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
console.log(`${C.red}${C.bold}✖ VALIDATION FAILED${C.reset}`);
|
|
186
|
-
logWitness(home, 'failed', { results: results });
|
|
187
|
-
process.exit(1);
|
|
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;
|
|
188
172
|
}
|
|
189
|
-
|
|
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;
|
|
190
178
|
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JOS VERIFY - Self-check: runs the embedded examples/hello.jos through jos-core
|
|
3
|
+
* Exit 0 when the kernel returns ok, 1 otherwise, 5 when no kernel is found.
|
|
4
|
+
*
|
|
5
|
+
* The check is hermetic: hello.jos is copied to a fresh temporary directory, which is also the HOME the kernel sees, so
|
|
6
|
+
* no `.jos/` chain of the user or of the folder where jos is installed applies, and the ledger of the run lands in that
|
|
7
|
+
* directory. The directory is removed at the end: verify leaves nothing in ~/.jos or anywhere else.
|
|
8
|
+
*/
|
|
9
|
+
'use strict';
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const os = require('os');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const pkg = require('../../package.json');
|
|
14
|
+
const kernel = require('../kernel');
|
|
15
|
+
const ui = require('../ui');
|
|
16
|
+
|
|
17
|
+
const HELLO = path.join(__dirname, '..', '..', 'examples', 'hello.jos');
|
|
18
|
+
|
|
19
|
+
exports.execute = async () => {
|
|
20
|
+
const out = ui.printer(process.stdout);
|
|
21
|
+
const { C } = out;
|
|
22
|
+
|
|
23
|
+
let k;
|
|
24
|
+
try { k = kernel.locate(); } catch (e) { return kernel.fail(e); }
|
|
25
|
+
const core = k.core;
|
|
26
|
+
out.header('JOS VERIFY', `jos ${pkg.version} | jos-core ${k.version} (${k.source})`);
|
|
27
|
+
|
|
28
|
+
out.line(`${C.bold}1. Kernel smoke test (examples/hello.jos)${C.reset}`);
|
|
29
|
+
let failed = false;
|
|
30
|
+
let tmp = null;
|
|
31
|
+
try {
|
|
32
|
+
tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'jos-verify-'));
|
|
33
|
+
const file = path.join(tmp, 'hello.jos');
|
|
34
|
+
fs.copyFileSync(HELLO, file);
|
|
35
|
+
// read once and run with the octets, as jos run does (jos-core 2.0.0-rc.8 verifies a 0.0.7 integrity_ref on them)
|
|
36
|
+
const { doc, bytes } = core.loadFileBytes(file);
|
|
37
|
+
const r = await core.run(doc, { cwd: tmp, dir: tmp, home: tmp, source: bytes, env: process.env });
|
|
38
|
+
if (r.ok === true && r.code === 0) {
|
|
39
|
+
out.ok('ok', `${r.artifact} (${r.duration_ms} ms)`);
|
|
40
|
+
} else {
|
|
41
|
+
out.fail(`not ok (code ${r.code})`, r.errors ? r.errors.join('; ') : '');
|
|
42
|
+
failed = true;
|
|
43
|
+
}
|
|
44
|
+
} catch (e) {
|
|
45
|
+
out.fail(e.message);
|
|
46
|
+
failed = true;
|
|
47
|
+
} finally {
|
|
48
|
+
if (tmp) fs.rmSync(tmp, { recursive: true, force: true });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
out.line();
|
|
52
|
+
if (failed) out.fail(`${C.bold}VERIFICATION FAILED`);
|
|
53
|
+
else out.ok(`${C.bold}SYSTEM HEALTHY`);
|
|
54
|
+
out.line();
|
|
55
|
+
return failed ? 1 : 0;
|
|
56
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JOS VERSION - host version and the jos-core kernel it found
|
|
3
|
+
* Two plain lines (scripts read them): `jos <version>` and `jos-core <version> (<source>: <file>)`; with color on a
|
|
4
|
+
* terminal the location is gray. Without a kernel the second line is `jos-core not found` and the exit code is 0.
|
|
5
|
+
*/
|
|
6
|
+
'use strict';
|
|
7
|
+
const pkg = require('../../package.json');
|
|
8
|
+
const kernel = require('../kernel');
|
|
9
|
+
const ui = require('../ui');
|
|
10
|
+
|
|
11
|
+
exports.execute = async () => {
|
|
12
|
+
const out = ui.printer(process.stdout);
|
|
13
|
+
const { C } = out;
|
|
14
|
+
out.line('jos ' + pkg.version);
|
|
15
|
+
try {
|
|
16
|
+
const k = kernel.locate();
|
|
17
|
+
out.line('jos-core ' + k.version + ` ${C.gray}(` + k.source + ': ' + k.file + `)${C.reset}`);
|
|
18
|
+
} catch (e) {
|
|
19
|
+
if (!(e instanceof kernel.KernelNotFound)) throw e;
|
|
20
|
+
out.line('jos-core not found');
|
|
21
|
+
}
|
|
22
|
+
return 0;
|
|
23
|
+
};
|