unoverse 0.1.199 → 0.1.200
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/lib/skills.mjs +32 -26
- package/package.json +1 -1
- package/vendor/base/lint/nodes/_schema/_defs.schema.json +1 -1
- package/vendor/base/lint/nodes/_schema/api.schema.json +1 -1
- package/vendor/base/lint/nodes/_schema/audio.schema.json +1 -1
- package/vendor/base/lint/nodes/_schema/config.schema.json +5 -5
- package/vendor/base/lint/nodes/_schema/credential.schema.json +3 -3
- package/vendor/base/lint/nodes/_schema/events.schema.json +6 -0
- package/vendor/base/lint/nodes/_schema/interface.schema.json +1 -1
- package/vendor/base/lint/nodes/_schema/narrate.schema.json +6 -0
- package/vendor/base/lint/nodes/_schema/node.schema.json +13 -5
- package/vendor/base/lint/nodes/_schema/package.schema.json +5 -5
- package/vendor/base/lint/nodes/_schema/run.schema.json +6 -0
- package/vendor/base/lint/nodes/_schema/service.schema.json +6 -0
- package/vendor/base/lint/nodes/_schema/test.schema.json +8 -8
- package/vendor/base/lint/nodes/_schema/toolExchange.schema.json +6 -0
- package/vendor/base/lint/nodes/schema.mjs +7 -7
package/lib/skills.mjs
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
// Install the Claude Code authoring skills for this developer, FROM THE DOCS SITE.
|
|
2
2
|
//
|
|
3
|
-
// The skills are
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
3
|
+
// The skills are files under packages/docs/skills/<skill>/, served by the docs site at the
|
|
4
|
+
// same path. They are NOT in the site's navigation (owner ruling 2026-09-05: a skill is
|
|
5
|
+
// for the agent to read, not a page for a person to browse), so the installer does not
|
|
6
|
+
// read llms.txt. It reads `skills/skills.json` for the list of skills, then each skill's
|
|
7
|
+
// SKILL.md, and follows every `references/<name>.md` the front page names. A reference the
|
|
8
|
+
// front page does not name never installs, and a test in packages/docs keeps the two in
|
|
9
|
+
// step.
|
|
8
10
|
//
|
|
9
11
|
// PER DEVELOPER, NOT PER PROJECT (2026-08-21). These install to ~/.claude/skills, so
|
|
10
12
|
// Claude Code picks them up in every folder the developer opens and nothing lands
|
|
@@ -20,18 +22,14 @@ import { tmpdir, homedir } from "node:os";
|
|
|
20
22
|
const DOCS = "https://docs.unoverse.ai";
|
|
21
23
|
|
|
22
24
|
/**
|
|
23
|
-
* The
|
|
24
|
-
*
|
|
25
|
-
* whatever case the site serves it in, because Claude Code looks for exactly that name.
|
|
26
|
-
* Pure, so it can be checked without a network.
|
|
25
|
+
* The reference files a skill's front page names, as `references/<name>.md`, in order of
|
|
26
|
+
* first mention and without repeats. Pure, so it can be checked without a network.
|
|
27
27
|
*/
|
|
28
|
-
export function
|
|
28
|
+
export function referencesNamedBy(skillMd) {
|
|
29
29
|
const out = [];
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const rel = basename(path).toLowerCase() === "skill.md" ? join(dirname(path), "SKILL.md") : path;
|
|
34
|
-
out.push({ skill, rel, url });
|
|
30
|
+
for (const m of skillMd.matchAll(/references\/([a-z0-9-]+)\.md/g)) {
|
|
31
|
+
const rel = `references/${m[1]}.md`;
|
|
32
|
+
if (!out.includes(rel)) out.push(rel);
|
|
35
33
|
}
|
|
36
34
|
return out;
|
|
37
35
|
}
|
|
@@ -90,30 +88,38 @@ const fresh = (url) => `${url}?t=${Date.now()}`;
|
|
|
90
88
|
|
|
91
89
|
export async function installSkills() {
|
|
92
90
|
const target = join(homedir(), ".claude", "skills");
|
|
93
|
-
let
|
|
91
|
+
let skills;
|
|
94
92
|
try {
|
|
95
|
-
const res = await fetch(fresh(`${DOCS}/
|
|
93
|
+
const res = await fetch(fresh(`${DOCS}/skills/skills.json`));
|
|
96
94
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
97
|
-
|
|
95
|
+
skills = (await res.json()).skills ?? [];
|
|
98
96
|
} catch {
|
|
99
97
|
console.log(` Skills install skipped (${DOCS} unreachable). They install on the next update.`);
|
|
100
98
|
return;
|
|
101
99
|
}
|
|
102
|
-
const skills = [...new Set(files.map((f) => f.skill))];
|
|
103
100
|
if (!skills.length) {
|
|
104
|
-
console.log(` Skills install skipped: ${DOCS}/
|
|
101
|
+
console.log(` Skills install skipped: ${DOCS}/skills/skills.json lists no skills.`);
|
|
105
102
|
return;
|
|
106
103
|
}
|
|
107
104
|
// Fetched whole into a scratch folder first, so a download that dies halfway leaves the
|
|
108
105
|
// installed copy untouched rather than half-replaced.
|
|
109
106
|
const tmp = mkdtempSync(join(tmpdir(), "unoverse-skills-"));
|
|
110
107
|
try {
|
|
111
|
-
for (const
|
|
112
|
-
const
|
|
113
|
-
if (!
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
108
|
+
for (const skill of skills) {
|
|
109
|
+
const front = await fetch(fresh(`${DOCS}/skills/${skill}/SKILL.md`));
|
|
110
|
+
if (!front.ok) throw new Error(`${skill}/SKILL.md: HTTP ${front.status}`);
|
|
111
|
+
const frontText = await front.text();
|
|
112
|
+
const files = [["SKILL.md", frontText]];
|
|
113
|
+
for (const rel of referencesNamedBy(frontText)) {
|
|
114
|
+
const res = await fetch(fresh(`${DOCS}/skills/${skill}/${rel}`));
|
|
115
|
+
if (!res.ok) throw new Error(`${skill}/${rel}: HTTP ${res.status}`);
|
|
116
|
+
files.push([rel, await res.text()]);
|
|
117
|
+
}
|
|
118
|
+
for (const [rel, text] of files) {
|
|
119
|
+
const p = join(tmp, skill, rel);
|
|
120
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
121
|
+
writeFileSync(p, skillFileText(skill, rel, text));
|
|
122
|
+
}
|
|
117
123
|
}
|
|
118
124
|
mkdirSync(target, { recursive: true });
|
|
119
125
|
// REPLACED WHOLE, ours only. Each skill folder the site carries is removed and
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
-
"$id": "https://unoverse/nodes/_defs.schema.json",
|
|
3
|
+
"$id": "https://docs.unoverse.ai/schemas/nodes/_defs.schema.json",
|
|
4
4
|
"title": "Shared node definitions",
|
|
5
5
|
"description": "Fragments $ref'd by the sibling node schemas. Not authored directly. See docs/architecture/DECLARATIVE_NODES.md.",
|
|
6
6
|
"definitions": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
-
"$id": "https://unoverse/nodes/api.schema.json",
|
|
3
|
+
"$id": "https://docs.unoverse.ai/schemas/nodes/api.schema.json",
|
|
4
4
|
"title": "Unoverse node API call",
|
|
5
5
|
"description": "api.yaml — the upstream call this node makes, and how its response maps onto the node's outputs. This is the file that replaces an executor. NAMED api, not service: `service` already means serviceConnectors / isService / MCP providers / /service-call in this platform. See docs/architecture/DECLARATIVE_NODES.md §8.\n\nEVERY enum below is the EXECUTOR'S CAPABILITY LIST. A manifest may only name a capability that is already implemented in code. Adding a value here without implementing it produces a node that lints clean and fails at run time, and allowing a manifest to name arbitrary code would end the safety property this whole format exists for (§2).\n\nA node is reached two ways, and they never cross. The GRAPH RUNS it: `run` is the ordered list of calls it makes, and `events` says what lands on its output connectors. Or a SERVICE CALLS it ad-hoc, exposed as MCP: `service` holds those methods, each with its own list of calls, each handing one value straight back to the caller rather than touching a connector. A pure service node has only `service`.\n\nCalls are ALWAYS a `run` list, even when there is one of them. There is no `request` key.",
|
|
6
6
|
"type": "object",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
-
"$id": "https://unoverse/nodes/audio.schema.json",
|
|
3
|
+
"$id": "https://docs.unoverse.ai/schemas/nodes/audio.schema.json",
|
|
4
4
|
"title": "Unoverse node audio lane",
|
|
5
5
|
"description": "api/audio.yaml — how a duplex voice node is wired to the platform's audio lane. Only meaningful beside a `transport: ws` call.\n\nWHY THIS FILE EXISTS, and it is not a design preference. There are TWO sockets in a voice call. The VENDOR socket lives in api/run.yaml and is the node's conversation with OpenAI, xAI or Nova. The AUDIO LANE is the platform's own socket to the browser, and it exists for exactly ONE reason: MCP cannot carry binary audio.\n\nSo the rule that keeps this file small: EVERYTHING THAT IS NOT AUDIO BELONGS IN `events`. Transcripts, tool results, speech state, usage — all of it reaches the client over MCP streaming already, by landing on an output connector like any other node's output. Nothing needs a side channel to get there. If something non-audio is being put in this file, the reason is almost always that it was easier to reach the lane than to declare an output, and that is the wrong trade: an output is visible on the canvas, wirable, and testable, and a side channel is none of those.\n\nWHAT THE EXECUTOR CONTRIBUTES. Everything here names computation rather than describing data, which is why it is a handful of keys rather than a body template. Resampling is an interpolation over samples. Coalescing is a timer and a byte budget. Barge-in is discarding a buffer rather than flushing it. None of those can be written as an expression, and all of them are identical for every vendor, which is exactly the test for belonging to the executor (DECLARATIVE_NODES.md §2).",
|
|
6
6
|
"type": "object",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
-
"$id": "https://unoverse/nodes/config.schema.json",
|
|
3
|
+
"$id": "https://docs.unoverse.ai/schemas/nodes/config.schema.json",
|
|
4
4
|
"title": "Unoverse node config",
|
|
5
|
-
"description": "config.yaml
|
|
5
|
+
"description": "config.yaml — the node's settings form. `configSchema` is itself a JSON Schema: this file describes the SHAPE OF THAT SCHEMA, not the shape of a config. Canvas renders the form from it and the executor resolves {{ config.* }} against the saved values.\n\nThis is the file with the most churn: every new option lands here and nowhere else. Full reference for the options: docs-starter/nodes/config-schema.md.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": [
|
|
8
8
|
"configSchema"
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
"enum": [
|
|
117
117
|
"modelTier"
|
|
118
118
|
],
|
|
119
|
-
"description": "An executor RESOLVER applied to this field's saved value just before the request is built. IMPLEMENTED resolvers only.\n\n`modelTier` is the migration of shared/models.ts resolveModel(): a saved workflow stores a concrete model id, and if that id is later retired the resolver falls back to the current best model in the same tier, warning the operator. Without it, replacing a model generation breaks every saved workflow that named an old id.\n\nThis is the
|
|
119
|
+
"description": "An executor RESOLVER applied to this field's saved value just before the request is built. IMPLEMENTED resolvers only.\n\n`modelTier` is the migration of shared/models.ts resolveModel(): a saved workflow stores a concrete model id, and if that id is later retired the resolver falls back to the current best model in the same tier, warning the operator. Without it, replacing a model generation breaks every saved workflow that named an old id.\n\nThis is the §2 line in miniature: the model LIST is description and lives in shared/models.yaml, while the fallback is computation and lives in the executor.",
|
|
120
120
|
"$comment": "Adding a value here without implementing it produces a node that lints clean and fails at run time."
|
|
121
121
|
},
|
|
122
122
|
"ui:field": {
|
|
@@ -137,7 +137,7 @@
|
|
|
137
137
|
"checkboxes",
|
|
138
138
|
"domainSelector"
|
|
139
139
|
],
|
|
140
|
-
"description": "IMPLEMENTED widgets only, verified against the renderer in apps/canvas/src/components/workflow/ConfigurationForm/FieldRenderer.jsx:\n\n toggle a boolean switch\n select an enum picker\n textarea a multi-line box (FieldRenderer.jsx:248). Pair with ui:options.rows\n slider a number, dragged rather than typed (:162)\n checkboxes an array of enum values, all visible at once (:208)\n domainSelector an array of hostnames (:195)\n\nNOT `color`. The retired Note node declared `ui:widget: color` and the renderer has never implemented it, so it fell through to a plain text input
|
|
140
|
+
"description": "IMPLEMENTED widgets only, verified against the renderer in apps/canvas/src/components/workflow/ConfigurationForm/FieldRenderer.jsx:\n\n toggle a boolean switch\n select an enum picker\n textarea a multi-line box (FieldRenderer.jsx:248). Pair with ui:options.rows\n slider a number, dragged rather than typed (:162)\n checkboxes an array of enum values, all visible at once (:208)\n domainSelector an array of hostnames (:195)\n\nNOT `color`. The retired Note node declared `ui:widget: color` and the renderer has never implemented it, so it fell through to a plain text input — a setting that looked bespoke and was not. Adding a value here without a branch in FieldRenderer reproduces exactly that: it lints clean and silently renders as something else."
|
|
141
141
|
},
|
|
142
142
|
"ui:dependencies": {
|
|
143
143
|
"type": "object",
|
|
@@ -171,4 +171,4 @@
|
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
|
-
}
|
|
174
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
-
"$id": "https://unoverse/nodes/credential.schema.json",
|
|
3
|
+
"$id": "https://docs.unoverse.ai/schemas/nodes/credential.schema.json",
|
|
4
4
|
"title": "Unoverse credential type",
|
|
5
|
-
"description": "credentials/<name>.yaml
|
|
5
|
+
"description": "credentials/<name>.yaml — the SHAPE of a credential, never its value. Declared once per package and referenced by name from each node.yaml.\n\nSecrets stay where they already live: encrypted in Postgres via credentialManager on a deployed universe, and in the developer's own .env locally (LOCAL_STUDIO.md). Only the schema is data in a manifest, which is why a manifest is safe to commit and to deploy.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": [
|
|
8
8
|
"name",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"name": {
|
|
45
45
|
"type": "string",
|
|
46
46
|
"pattern": "^[a-z][A-Za-z0-9_]*$",
|
|
47
|
-
"description": "The FIELD name, as a manifest reads it: credentials.<credentialName>.<fieldName>.\n\ncamelCase by convention. snake_case is ALLOWED, for exactly the reason a PORT name may be snake_case (_defs.schema.json): a migrated node must keep the names its code version used. A credential is read field by field out of stored, encrypted values
|
|
47
|
+
"description": "The FIELD name, as a manifest reads it: credentials.<credentialName>.<fieldName>.\n\ncamelCase by convention. snake_case is ALLOWED, for exactly the reason a PORT name may be snake_case (_defs.schema.json): a migrated node must keep the names its code version used. A credential is read field by field out of stored, encrypted values — Cloudinary's are cloud_name, api_key and api_secret — so tidying them to camelCase would leave every existing workflow authenticating with undefined, and it would fail as a vendor 401 rather than as anything naming the rename. Fidelity beats house style here.\n\nPrefer camelCase for a NEW credential, where nothing is stored yet."
|
|
48
48
|
},
|
|
49
49
|
"displayName": {
|
|
50
50
|
"type": "string"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
-
"$id": "https://unoverse/nodes/interface.schema.json",
|
|
3
|
+
"$id": "https://docs.unoverse.ai/schemas/nodes/interface.schema.json",
|
|
4
4
|
"title": "Unoverse node interface",
|
|
5
5
|
"description": "interface.yaml — the node's WIRING SURFACE, and nothing else. Everything another node, an agent, or a developer needs in order to connect to this one: what goes in, what comes out, what services it offers or consumes, and what credentials it needs.\n\nIt has its own file because this is the question asked most often about a node, and it should never require reading past a node's branding to answer. See docs/architecture/DECLARATIVE_NODES.md §5.",
|
|
6
6
|
"type": "object",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
-
"$id": "https://unoverse/nodes/node.schema.json",
|
|
3
|
+
"$id": "https://docs.unoverse.ai/schemas/nodes/node.schema.json",
|
|
4
4
|
"title": "Unoverse node",
|
|
5
5
|
"description": "node.yaml — what this node IS: its identity, its kind, and how it is discovered. The only required file in a node folder.\n\nThe wiring surface lives next door in interface.yaml, so \"what can I connect to this?\" is answered without reading past the node's branding. config/api/test/interface may each be inlined here instead of living in their own file, but never both (that is a lint error, not a merge). See docs/architecture/DECLARATIVE_NODES.md §5.",
|
|
6
6
|
"type": "object",
|
|
@@ -89,23 +89,31 @@
|
|
|
89
89
|
"cacheable": {
|
|
90
90
|
"description": "Output may be MEMOIZED: the engine may serve a prior run's output when the fingerprint (type + version + RESOLVED config + credential ref + scope) matches.\n\nSet ONLY for idempotent side-effect-free READS (search, scrape, fetch-by-id, pure transform). NEVER for effectful nodes (send, write, post, charge) where reuse silently skips the side effect, and NEVER for non-deterministic ones (LLM completions, time or random dependent) where re-running is the correct behaviour. Memoization is off engine-wide by default; this flag only makes the node eligible.\n\nOBJECT FORM, for nodes whose config carries a VOLATILE field (a presigned/expiring URL) over content whose identity arrives on an input (an etag). `ignore` drops the named top-level resolved-config fields from the fingerprint; `key` names input leaf fields (dot-suffix match, e.g. \"etag\" or \"file.etag\") the engine collects from the resolved inputs as the content's identity. If a key field collects NOTHING on a run, that run is not cached — no identity, no reuse. Declaring the object form IS the opt-in.",
|
|
91
91
|
"oneOf": [
|
|
92
|
-
{
|
|
92
|
+
{
|
|
93
|
+
"type": "boolean"
|
|
94
|
+
},
|
|
93
95
|
{
|
|
94
96
|
"type": "object",
|
|
95
97
|
"properties": {
|
|
96
98
|
"ignore": {
|
|
97
99
|
"type": "array",
|
|
98
|
-
"items": {
|
|
100
|
+
"items": {
|
|
101
|
+
"type": "string"
|
|
102
|
+
},
|
|
99
103
|
"description": "Top-level resolved-config fields excluded from the fingerprint because they are volatile, not identity (a presigned URL that changes every run). Every OTHER config field still busts the cache."
|
|
100
104
|
},
|
|
101
105
|
"key": {
|
|
102
106
|
"type": "array",
|
|
103
|
-
"items": {
|
|
107
|
+
"items": {
|
|
108
|
+
"type": "string"
|
|
109
|
+
},
|
|
104
110
|
"minItems": 1,
|
|
105
111
|
"description": "Input leaf fields that identify the content, matched by dot-suffix against every leaf path in the resolved inputs (wiring-independent: \"etag\" matches inputs.file.<anyNode>.file.etag). All matches, sorted by path, enter the fingerprint. A key that matches nothing disables caching for that run."
|
|
106
112
|
}
|
|
107
113
|
},
|
|
108
|
-
"required": [
|
|
114
|
+
"required": [
|
|
115
|
+
"key"
|
|
116
|
+
],
|
|
109
117
|
"additionalProperties": false
|
|
110
118
|
}
|
|
111
119
|
]
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
-
"$id": "https://unoverse/nodes/package.schema.json",
|
|
3
|
+
"$id": "https://docs.unoverse.ai/schemas/nodes/package.schema.json",
|
|
4
4
|
"title": "Unoverse node package",
|
|
5
|
-
"description": "package.yaml
|
|
5
|
+
"description": "package.yaml — the envelope for a folder of nodes. Replaces the `gravity` block in package.json for packages that no longer ship code. See docs/architecture/DECLARATIVE_NODES.md §5.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": [
|
|
8
8
|
"name",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"type": "string",
|
|
62
62
|
"pattern": "^(\\*$|(\\*\\*\\.|\\*\\.)?[a-z0-9-]+(\\.[a-z0-9-]+)+$)"
|
|
63
63
|
},
|
|
64
|
-
"description": "The ONLY hosts this package's nodes may call. The executor refuses any other, and lint refuses a request URL outside it.\n\nThis is the control that makes a manifest safe to accept from someone else. A manifest cannot execute, but it CAN say \"POST this credential to evil.example\", and that is exfiltration without a line of code. Bounding allowedHosts means a package's secrets can only ever reach hosts it declared in writing, and the declaration is one short reviewable list at install time.\n\nSECURITY.md draws the line as: a NODE is trusted code bounded by provenance; a TEMPLATE EXPRESSION is untrusted data bounded by having no credentials in scope. A manifest node is neither
|
|
64
|
+
"description": "The ONLY hosts this package's nodes may call. The executor refuses any other, and lint refuses a request URL outside it.\n\nThis is the control that makes a manifest safe to accept from someone else. A manifest cannot execute, but it CAN say \"POST this credential to evil.example\", and that is exfiltration without a line of code. Bounding allowedHosts means a package's secrets can only ever reach hosts it declared in writing, and the declaration is one short reviewable list at install time.\n\nSECURITY.md draws the line as: a NODE is trusted code bounded by provenance; a TEMPLATE EXPRESSION is untrusted data bounded by having no credentials in scope. A manifest node is neither — it arrives as data but holds a credential and a URL. AllowedHosts is its boundary.\n\n`*.` matches ONE subdomain level, e.g. \"*.slack.com\" reaches api.slack.com but not a.b.slack.com.\n\n`**.` matches ANY depth, and is written with two stars because it is a deliberately weaker claim the author should have to make on purpose. AWS forced it: `dynamodb.us-east-1.amazonaws.com` is two labels deep and an S3 bucket is three, since the region AND the bucket are part of the host. Listing every region defeats itself the day AWS adds one, and a bucket name comes from config so it cannot be listed at all. Still bounded: \"**.amazonaws.com\" reaches any AWS host and nothing else.\n\n`*` alone means ANY HOST, and it applies ONLY to calls that send no credential. Some nodes legitimately fetch a url a person supplies (an image for a model to look at, a document to read) and that url cannot be declared in advance. It is safe exactly there: this boundary exists to stop exfiltration, and a call with no `auth` block has no credential to leak. Non-https is still refused, so cloud metadata and plaintext internal services stay out of reach, and the moment a call carries a credential the wildcard stops matching and the declared list applies.",
|
|
65
65
|
"examples": [
|
|
66
66
|
[
|
|
67
67
|
"api.openai.com"
|
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
},
|
|
134
134
|
"poll": {
|
|
135
135
|
"type": "boolean",
|
|
136
|
-
"description": "True when a node waits on an asynchronous JOB: start it, then ask until it is done. On an executor without it the node settles on the START reply, which is a receipt carrying a job id, and emits that as its answer
|
|
136
|
+
"description": "True when a node waits on an asynchronous JOB: start it, then ask until it is done. On an executor without it the node settles on the START reply, which is a receipt carrying a job id, and emits that as its answer — every downstream field reads empty and nothing errors."
|
|
137
137
|
},
|
|
138
138
|
"encoding": {
|
|
139
139
|
"type": "array",
|
|
@@ -145,7 +145,7 @@
|
|
|
145
145
|
"ndjson"
|
|
146
146
|
]
|
|
147
147
|
},
|
|
148
|
-
"description": "Body encodings this package's nodes use. Declared like auth and transport, and for the same reason: on an executor without one, the body is sent as plain JSON instead. That is not an error anywhere
|
|
148
|
+
"description": "Body encodings this package's nodes use. Declared like auth and transport, and for the same reason: on an executor without one, the body is sent as plain JSON instead. That is not an error anywhere — the vendor simply rejects a shape it did not expect, or worse accepts it and ignores the file, so an upload silently transcribes nothing."
|
|
149
149
|
},
|
|
150
150
|
"renderComponents": {
|
|
151
151
|
"type": "boolean",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
-
"$id": "https://unoverse/nodes/test.schema.json",
|
|
3
|
+
"$id": "https://docs.unoverse.ai/schemas/nodes/test.schema.json",
|
|
4
4
|
"title": "Unoverse node test data",
|
|
5
|
-
"description": "test.yaml
|
|
5
|
+
"description": "test.yaml — the fixture behind Studio's \"Load sample, Run\" button and behind `unoverse node test`. Every node must have one: a node nobody can run is a node nobody can trust, and lint enforces it.\n\nTemplate fields are supplied ALREADY RESOLVED, because the bench has no upstream node to resolve them from. See docs/architecture/DECLARATIVE_NODES.md §6.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": [
|
|
8
8
|
"testData"
|
|
@@ -42,11 +42,11 @@
|
|
|
42
42
|
"additionalProperties": false
|
|
43
43
|
},
|
|
44
44
|
"state": {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
"type": "object",
|
|
46
|
+
"description": "PLATFORM STORAGE this fixture starts with, keyed by LOGICAL key. The bench has no Redis, so it runs the real state and loop code against an in-memory client seeded from here; a value is either an object (stored whole) or an array (stored as a list, for drain).\n\nIt exists because some nodes only make sense with a warm store. `LoopEnd` closes a pass of a loop that `LoopStart` opened, so with nothing seeded its very first act is the \"loop state not found\" error it is supposed to raise for a mistyped id — the bench would report a real bug as the node's normal behaviour. Same for any node whose first call is a cache read.\n\nThe run's ids are FIXED on the bench (see test-node.mjs) so a key naming them is predictable.",
|
|
47
|
+
"additionalProperties": true
|
|
48
|
+
},
|
|
49
|
+
"expect": {
|
|
50
50
|
"type": "object",
|
|
51
51
|
"description": "Assertions over the result. On the workflow channel the scope is `output` (the emitted connectors); on the service channel it is `output` too, bound to the value the method RETURNED. Absent means the run only has to succeed.",
|
|
52
52
|
"additionalProperties": {
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"required": [
|
|
59
59
|
"method"
|
|
60
60
|
],
|
|
61
|
-
"description": "For a node with `provides`: which SERVICE method to exercise and with what arguments. A pure service node has no inputs to feed and no outputs to watch, so `inputs` cannot stand in
|
|
61
|
+
"description": "For a node with `provides`: which SERVICE method to exercise and with what arguments. A pure service node has no inputs to feed and no outputs to watch, so `inputs` cannot stand in — the only way to run it is to call it the way a consumer would.",
|
|
62
62
|
"properties": {
|
|
63
63
|
"method": {
|
|
64
64
|
"type": "string",
|
|
@@ -25,13 +25,13 @@ try {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export const SCHEMA_ID = {
|
|
28
|
-
node: "https://unoverse/nodes/node.schema.json",
|
|
29
|
-
interface: "https://unoverse/nodes/interface.schema.json",
|
|
30
|
-
config: "https://unoverse/nodes/config.schema.json",
|
|
31
|
-
api: "https://unoverse/nodes/api.schema.json",
|
|
32
|
-
test: "https://unoverse/nodes/test.schema.json",
|
|
33
|
-
package: "https://unoverse/nodes/package.schema.json",
|
|
34
|
-
credential: "https://unoverse/nodes/credential.schema.json",
|
|
28
|
+
node: "https://docs.unoverse.ai/schemas/nodes/node.schema.json",
|
|
29
|
+
interface: "https://docs.unoverse.ai/schemas/nodes/interface.schema.json",
|
|
30
|
+
config: "https://docs.unoverse.ai/schemas/nodes/config.schema.json",
|
|
31
|
+
api: "https://docs.unoverse.ai/schemas/nodes/api.schema.json",
|
|
32
|
+
test: "https://docs.unoverse.ai/schemas/nodes/test.schema.json",
|
|
33
|
+
package: "https://docs.unoverse.ai/schemas/nodes/package.schema.json",
|
|
34
|
+
credential: "https://docs.unoverse.ai/schemas/nodes/credential.schema.json",
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
/** The four sections that may live in their own file OR inline in node.yaml. */
|