structured-context 0.11.0 → 0.13.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/README.md +52 -198
- package/dist/api.d.ts +17 -15
- package/dist/api.js +12 -9
- package/dist/commands/diagram.d.ts +1 -0
- package/dist/commands/diagram.js +7 -1
- package/dist/commands/docs.js +1 -0
- package/dist/commands/dump.d.ts +15 -2
- package/dist/commands/dump.js +18 -4
- package/dist/commands/providers.d.ts +4 -0
- package/dist/commands/providers.js +52 -0
- package/dist/commands/render.d.ts +4 -0
- package/dist/commands/render.js +64 -9
- package/dist/commands/schemas.d.ts +2 -2
- package/dist/commands/schemas.js +133 -22
- package/dist/commands/show.d.ts +1 -0
- package/dist/commands/show.js +7 -1
- package/dist/commands/spaces.d.ts +3 -1
- package/dist/commands/spaces.js +11 -2
- package/dist/commands/validate-file.d.ts +3 -0
- package/dist/commands/validate-file.js +24 -6
- package/dist/commands/validate.d.ts +4 -0
- package/dist/commands/validate.js +79 -23
- package/dist/config.d.ts +25 -1
- package/dist/config.js +106 -12
- package/dist/filter/augment-nodes.d.ts +3 -1
- package/dist/filter/augment-nodes.js +12 -7
- package/dist/filter/filter-nodes.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +96 -31
- package/dist/judge/context.d.ts +20 -0
- package/dist/judge/context.js +105 -0
- package/dist/judge/factory.d.ts +44 -0
- package/dist/judge/factory.js +148 -0
- package/dist/judge/index.d.ts +13 -0
- package/dist/judge/index.js +8 -0
- package/dist/judge/judge-node.d.ts +27 -0
- package/dist/judge/judge-node.js +122 -0
- package/dist/judge/prompt.d.ts +10 -0
- package/dist/judge/prompt.js +92 -0
- package/dist/judge/provider/anthropic.d.ts +17 -0
- package/dist/judge/provider/anthropic.js +43 -0
- package/dist/judge/provider/fake.d.ts +26 -0
- package/dist/judge/provider/fake.js +56 -0
- package/dist/judge/provider/jev.d.ts +37 -0
- package/dist/judge/provider/jev.js +169 -0
- package/dist/judge/provider/openai-decisions.d.ts +21 -0
- package/dist/judge/provider/openai-decisions.js +84 -0
- package/dist/judge/provider/openai.d.ts +20 -0
- package/dist/judge/provider/openai.js +53 -0
- package/dist/judge/provider/per-rule.d.ts +10 -0
- package/dist/judge/provider/per-rule.js +18 -0
- package/dist/judge/trim.d.ts +27 -0
- package/dist/judge/trim.js +62 -0
- package/dist/judge/types.d.ts +92 -0
- package/dist/judge/types.js +9 -0
- package/dist/judge/verdicts.d.ts +24 -0
- package/dist/judge/verdicts.js +82 -0
- package/dist/load-space-graph.d.ts +7 -5
- package/dist/load-space-graph.js +19 -8
- package/dist/plugins/loader.js +3 -3
- package/dist/plugins/markdown/extract-content-links.js +8 -29
- package/dist/plugins/markdown/index.js +8 -2
- package/dist/plugins/markdown/parse-embedded.d.ts +8 -0
- package/dist/plugins/markdown/parse-embedded.js +195 -24
- package/dist/plugins/markdown/read-space.js +42 -14
- package/dist/plugins/markdown/render-directory.d.ts +11 -0
- package/dist/plugins/markdown/render-directory.js +130 -0
- package/dist/plugins/markdown/template-sync.js +3 -4
- package/dist/plugins/markdown/util.d.ts +26 -5
- package/dist/plugins/markdown/util.js +56 -15
- package/dist/plugins/mermaid/render-mermaid.js +2 -1
- package/dist/plugins/miro/sync.js +7 -6
- package/dist/plugins/util.d.ts +39 -1
- package/dist/plugins/util.js +139 -0
- package/dist/read/resolve-graph-edges.d.ts +1 -1
- package/dist/read/resolve-graph-edges.js +46 -27
- package/dist/read/wikilink-utils.d.ts +17 -0
- package/dist/read/wikilink-utils.js +40 -0
- package/dist/render/render.d.ts +3 -1
- package/dist/render/render.js +2 -2
- package/dist/schema/evaluate-rule.d.ts +33 -2
- package/dist/schema/evaluate-rule.js +58 -4
- package/dist/schema/gating.d.ts +69 -0
- package/dist/schema/gating.js +253 -0
- package/dist/schema/meta-schemas.d.ts +13 -0
- package/dist/schema/meta-schemas.js +50 -0
- package/dist/schema/metadata-contract.d.ts +952 -183
- package/dist/schema/metadata-contract.js +177 -33
- package/dist/schema/rule-filter.d.ts +12 -0
- package/dist/schema/rule-filter.js +46 -0
- package/dist/schema/rule-severity.d.ts +13 -0
- package/dist/schema/rule-severity.js +33 -0
- package/dist/schema/schema.d.ts +14 -8
- package/dist/schema/schema.js +308 -199
- package/dist/schema/validate-graph.d.ts +2 -1
- package/dist/schema/validate-graph.js +40 -23
- package/dist/schema/validate-rules.d.ts +16 -3
- package/dist/schema/validate-rules.js +91 -29
- package/dist/schemas/_knowledge_wiki.json +24 -24
- package/dist/schemas/_okf.json +172 -0
- package/dist/schemas/_ost_strict.json +17 -8
- package/dist/schemas/_sctx_base.json +3 -3
- package/dist/schemas/_strategy_general.json +38 -38
- package/dist/schemas/generated/0.12.0/_structured_context_schema_meta.json +229 -0
- package/dist/schemas/generated/0.13.0/_structured_context_schema_meta.json +400 -0
- package/dist/schemas/generated/latest/_structured_context_schema_meta.json +400 -0
- package/dist/schemas/knowledge_wiki.json +10 -9
- package/dist/schemas/okf.json +8 -0
- package/dist/schemas/strategy_general.json +15 -14
- package/dist/schemas/strict_ost.json +17 -16
- package/dist/space-context.d.ts +5 -2
- package/dist/space-context.js +9 -4
- package/dist/space-graph.d.ts +9 -7
- package/dist/space-graph.js +7 -5
- package/dist/testing.d.ts +33 -0
- package/dist/testing.js +72 -0
- package/dist/types.d.ts +64 -16
- package/dist/validate.d.ts +62 -9
- package/dist/validate.js +229 -114
- package/docs/concepts.md +65 -9
- package/docs/config.md +78 -1
- package/docs/rules.md +168 -50
- package/docs/schemas.md +152 -52
- package/package.json +17 -11
- package/schemas/_knowledge_wiki.json +24 -24
- package/schemas/_okf.json +172 -0
- package/schemas/_ost_strict.json +17 -8
- package/schemas/_sctx_base.json +3 -3
- package/schemas/_strategy_general.json +38 -38
- package/schemas/generated/0.12.0/_structured_context_schema_meta.json +229 -0
- package/schemas/generated/0.13.0/_structured_context_schema_meta.json +400 -0
- package/schemas/generated/latest/_structured_context_schema_meta.json +400 -0
- package/schemas/knowledge_wiki.json +10 -9
- package/schemas/okf.json +8 -0
- package/schemas/strategy_general.json +15 -14
- package/schemas/strict_ost.json +17 -16
- package/dist/schemas/generated/_structured_context_schema_meta.json +0 -191
- package/schemas/generated/_structured_context_schema_meta.json +0 -191
package/docs/config.md
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
title: "Configuration Reference"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Configuration Reference
|
|
2
6
|
|
|
3
7
|
## Config file location
|
|
4
8
|
|
|
@@ -26,6 +30,12 @@ A space is a named directory or single file registered in the config. Example:
|
|
|
26
30
|
}
|
|
27
31
|
```
|
|
28
32
|
|
|
33
|
+
**`schema`** — the schema used to validate nodes in the space. Can be:
|
|
34
|
+
- A bundled schema name (e.g. `strategy_general.json`, `strict_ost.json`)
|
|
35
|
+
- A local path relative to the config file (e.g. `./schemas/custom.json`) or absolute path
|
|
36
|
+
- A plugin schema prefixed with the plugin name (e.g. `sctx-wardley-mapping/wardley_map.json` or `wardley-mapping/wardley_map.json`). Specifying a plugin-prefixed schema automatically loads that plugin for the space.
|
|
37
|
+
- A simple filename (e.g. `wardley_map.json`) provided by a plugin explicitly declared under `plugins`.
|
|
38
|
+
|
|
29
39
|
**`includeSpacesFrom`** — import space definitions from other config files. Useful for aggregating spaces from multiple projects into a central config. Duplicate space names are not allowed.
|
|
30
40
|
|
|
31
41
|
A config must define at least one of `spaces` or `includeSpacesFrom`. When using `includeSpacesFrom` alone, `spaces` can be omitted entirely.
|
|
@@ -53,6 +63,73 @@ Use `plugins` to load parse plugins that read spaces from non-markdown sources.
|
|
|
53
63
|
|
|
54
64
|
All plugin names must start with `sctx-` (the prefix is optional in config and normalised on load). The special name `markdown` refers to the built-in markdown plugin. External plugins are resolved in order: config-adjacent (`{configDir}/plugins/{name}`), then npm. Each plugin must export a `configSchema` JSON Schema; config is validated against it on load. Fields annotated `format: 'path'` in a plugin's `configSchema` are resolved relative to the config file directory.
|
|
55
65
|
|
|
66
|
+
Plugins can also contribute schemas and partials via a `<pluginRoot>/schemas/` directory (or `"sctx": { "schemas": "..." }` in `package.json`). If a space specifies a plugin-prefixed schema (e.g. `"sctx-wardley-mapping/wardley_map.json"`), schemas are resolved statically without executing plugin code, and the plugin's code hooks are loaded when parsing or rendering.
|
|
67
|
+
|
|
68
|
+
## Model providers and semantic rules
|
|
69
|
+
|
|
70
|
+
structured-context evaluates [semantic rules](semantic-rules.md) with a **model provider**: a named definition that says which wire API to speak, which model to use and where. A provider definition combines an `api`, explicit values or environment-variable names, and the key source.
|
|
71
|
+
|
|
72
|
+
**Bundled providers** work without any configuration: a fixed model per vendor chosen by this project, plus `-env` variants that also read the vendor's usual model and endpoint variables (`OPENAI_MODEL`, `OPENAI_BASE_URL`, `ANTHROPIC_MODEL`), falling back to the bundle's defaults where a variable is unset. Bundles without `-env` read no environment beyond the key: they behave the same on every machine.
|
|
73
|
+
|
|
74
|
+
Run `sctx providers` for the authoritative list of bundled and configured providers with their effective API, endpoint, model and key variable. Keys are never shown. `bun run test:models` checks that each bundled model resolves on its endpoint.
|
|
75
|
+
|
|
76
|
+
### Wire APIs and bundled-provider conventions
|
|
77
|
+
|
|
78
|
+
A provider's `api` names the **request shape**, potentially supported by multiple vendors.
|
|
79
|
+
|
|
80
|
+
| API | Shape | Bundled providers |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| `openrouter-decisions` | Decisions API, closed-set `choice` questions, answers with confidence and probabilities | `openrouter-jev`, `openrouter-openai-decisions` |
|
|
83
|
+
| `typesafe-systemone` | Same decisions shape (currently) | `typesafe-jev` |
|
|
84
|
+
| `openai-decisions` | OpenAI's Decisions API: same concept, its own request/response shape | `openai-decisions` |
|
|
85
|
+
| `anthropic-messages` | Chat: structured JSON verdict | `anthropic`, `anthropic-haiku-5-5`, `anthropic-env` |
|
|
86
|
+
| `openai-completions` | Chat: OpenAI-compatible; works with Ollama, vLLM and OpenRouter | `openai`, `openai-env`, `openrouter-openai` |
|
|
87
|
+
|
|
88
|
+
Bundled provider names follow the pattern:
|
|
89
|
+
|
|
90
|
+
- `<vendor>` — this project's recommended model for that vendor; can change between releases.
|
|
91
|
+
- `<vendor>-<model>` (e.g. `anthropic-haiku-5-5`) — pins a model id, so it does not drift when the `<vendor>` bundle's recommendation moves.
|
|
92
|
+
- `<vendor>-env` — hydrates model, endpoint etc from the vendor's usual environment variables.
|
|
93
|
+
- `openrouter-<vendor>-<purpose>` (e.g. `openrouter-openai-decisions`) — aggregating vendor's endpoint variant
|
|
94
|
+
|
|
95
|
+
**Configured providers** live in `modelProviders` at the top level, and replace a bundled provider of the same name. Fields a configured provider leaves out still take the API defaults:
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"modelProviders": {
|
|
100
|
+
"mine": { "api": "openrouter-decisions", "apiKeyEnv": "MY_JEV_KEY" },
|
|
101
|
+
"local": { "api": "openai-completions", "endpoint": "http://localhost:11434/v1" }
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
| Field | Description |
|
|
107
|
+
|---|---|
|
|
108
|
+
| `api` | **Required.** The wire API (see the table above and [Choosing a model provider](semantic-rules.md#choosing-a-model-provider)), or `fake` (for tests). |
|
|
109
|
+
| `model` | A fixed model. Its default depends on the API. |
|
|
110
|
+
| `modelEnv` | Environment variable holding a model override. An explicit `model` wins; a variable set but empty is an error. |
|
|
111
|
+
| `endpoint` | A fixed API URL. |
|
|
112
|
+
| `endpointEnv` | Environment variable holding an endpoint override. Same precedence as `modelEnv`. |
|
|
113
|
+
| `apiKeyEnv` | The environment variable that holds the key. Defaults to the API's usual variable (see the table above). A local `openai-completions` endpoint can need no key. |
|
|
114
|
+
| `apiKey` | The key itself. Use `apiKeyEnv` instead, so that keys stay out of config files. |
|
|
115
|
+
|
|
116
|
+
Resolution per field: the explicit value wins, then the named environment variable, then the API default. Provider implementations read no environment; all hydration happens before one runs.
|
|
117
|
+
|
|
118
|
+
The environment holds only keys and the variables a `-env` bundle names. No environment variable selects a provider.
|
|
119
|
+
|
|
120
|
+
The top-level `semantic.provider` names the model provider that evaluates semantic rules: a bundled provider or an entry in `modelProviders`. A space can set its own `semantic`, which replaces the top-level one for that space, for example to keep a private space on a local model:
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"semantic": { "provider": "openrouter-jev" },
|
|
125
|
+
"spaces": [
|
|
126
|
+
{ "name": "private-notes", "path": "/path/to/notes", "semantic": { "provider": "local" } }
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Config does not turn semantic evaluation on. Each run asks for it with `--semantic`. `--semantic <provider>` also selects a different provider for that run.
|
|
132
|
+
|
|
56
133
|
## Markdown plugin config
|
|
57
134
|
|
|
58
135
|
Set under `plugins.markdown` per space.
|
package/docs/rules.md
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Executable Rules"
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# Executable Rules
|
|
2
6
|
|
|
3
|
-
Rules
|
|
7
|
+
Rules check content after JSON Schema validation, for example cross-node checks and workflow constraints. A schema declares rules in `$metadata.rules`. A rule has an `expression`, a `semantic` part, or both:
|
|
8
|
+
|
|
9
|
+
- An **`expression`** is a JSONata test that must return `true`.
|
|
10
|
+
- A **`semantic`** part asks a model to evaluate a node. See [Semantic rules](semantic-rules.md).
|
|
11
|
+
|
|
12
|
+
A rule with neither is a schema error.
|
|
4
13
|
|
|
5
14
|
For metadata structure and composition behavior, see [docs/schemas.md](schemas.md).
|
|
6
15
|
|
|
@@ -14,40 +23,76 @@ Rules are a flat array.
|
|
|
14
23
|
"id": "active-outcome-count",
|
|
15
24
|
"category": "workflow",
|
|
16
25
|
"description": "Only one outcome should be active at a time",
|
|
17
|
-
"
|
|
18
|
-
"
|
|
26
|
+
"global": true,
|
|
27
|
+
"expression": "$count(nodes[resolvedType='outcome' and status='active']) <= 1"
|
|
19
28
|
}
|
|
20
29
|
]
|
|
21
30
|
```
|
|
22
31
|
|
|
23
|
-
|
|
32
|
+
Every rule has a category, which describes its intent and supplies a default severity.
|
|
33
|
+
|
|
34
|
+
| Category | Typical use | Default severity |
|
|
24
35
|
|---|---|---|
|
|
25
|
-
| `
|
|
26
|
-
| `
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
29
|
-
| `type` | no | Restrict rule to nodes of this `resolvedType` |
|
|
30
|
-
| `scope` | no | Use `"global"` to evaluate once for the whole space |
|
|
31
|
-
| `override` | no | Only for merge conflicts: allows later duplicate `id` to replace earlier |
|
|
36
|
+
| `validation` | Hard correctness constraints | `error` |
|
|
37
|
+
| `coherence` | Cross-node consistency checks | `warning` |
|
|
38
|
+
| `workflow` | Process/operating-discipline checks | `warning` |
|
|
39
|
+
| `best-practice` | Advisory quality checks | `info` |
|
|
32
40
|
|
|
33
|
-
|
|
41
|
+
Severity can also be set per rule. It applies to a failed `expression` and to the default `fail` verdict of a [semantic rule](semantic-rules.md#severity).
|
|
34
42
|
|
|
35
|
-
|
|
43
|
+
| Severity | Meaning | Fails validation |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| `error` | Content is wrong | yes |
|
|
46
|
+
| `warning` | Content is likely inconsistent or incomplete | no |
|
|
47
|
+
| `info` | Content could be better | no |
|
|
36
48
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
49
|
+
Only `error` violations fail validation: `sctx validate` and `sctx validate-file` exit non-zero, and JSON output reports `valid: false`. Warnings and info violations are reported but don't fail, so a work-in-progress space can still pass while advisory checks remain visible.
|
|
50
|
+
|
|
51
|
+
Prefer choosing the right category and relying on its default. Set `severity` on an individual rule only when that rule genuinely differs from the rest of its category:
|
|
52
|
+
|
|
53
|
+
```json5
|
|
54
|
+
{
|
|
55
|
+
"id": "goal-not-blocked",
|
|
56
|
+
"category": "workflow",
|
|
57
|
+
"severity": "error",
|
|
58
|
+
"description": "Goals must not be left blocked",
|
|
59
|
+
"type": "goal",
|
|
60
|
+
"expression": "current.status != 'blocked'"
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`sctx schemas show` lists each category with its default severity and marks rules that override it.
|
|
65
|
+
|
|
66
|
+
## Selecting nodes
|
|
67
|
+
|
|
68
|
+
- A rule with `global: true` runs once for the space. It needs an `expression`, and it cannot have `when`, `type` or `semantic`.
|
|
69
|
+
- Other rules run once for each node.
|
|
70
|
+
- `type` limits a rule to nodes with that `resolvedType`.
|
|
71
|
+
- `when` limits a rule to nodes where the applicability condition returns `true`. See [Conditional rules](#conditional-rules-when).
|
|
72
|
+
|
|
73
|
+
### Conditional rules (`when`)
|
|
43
74
|
|
|
44
|
-
|
|
75
|
+
`when` is the rule's applicability condition: a JSONata test that determines whether a rule applies to a node. It has the same context as `expression` and runs before it. Use `when` when `type` cannot select the nodes a rule applies to:
|
|
45
76
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
77
|
+
```json
|
|
78
|
+
{ "id": "ready-has-owner", "category": "workflow", "type": "task",
|
|
79
|
+
"description": "Ready tasks have an owner",
|
|
80
|
+
"when": "current.status = 'ready'", "expression": "$exists(current.owner)" }
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`when` costs almost nothing, so use it to stop an expensive rule from running where it does not apply. For a semantic rule, this saves a model call on each node that `when` excludes.
|
|
84
|
+
|
|
85
|
+
### Order within a rule
|
|
86
|
+
|
|
87
|
+
A rule runs its parts in this order: `when`, then `expression`, then the `semantic` part. If the `expression` fails, the `semantic` part does not run on that node.
|
|
88
|
+
|
|
89
|
+
An error in a `when` or `expression` is reported as a non-gating `error` violation.
|
|
49
90
|
|
|
50
|
-
##
|
|
91
|
+
## Writing expressions
|
|
92
|
+
|
|
93
|
+
An `expression` (and `when`) is JSONata. This section covers what an expression can see and how to write expressions.
|
|
94
|
+
|
|
95
|
+
### Expression context
|
|
51
96
|
|
|
52
97
|
Each evaluation receives:
|
|
53
98
|
|
|
@@ -65,7 +110,7 @@ Useful resolved fields on nodes:
|
|
|
65
110
|
|
|
66
111
|
Prefer `resolvedType` over raw `type` so aliases are handled correctly.
|
|
67
112
|
|
|
68
|
-
|
|
113
|
+
### Predicate scoping (`$$`)
|
|
69
114
|
|
|
70
115
|
Inside `nodes[...]`, bare names refer to each candidate node. Use `$$` to reference outer variables:
|
|
71
116
|
|
|
@@ -73,29 +118,39 @@ Inside `nodes[...]`, bare names refer to each candidate node. Use `$$` to refere
|
|
|
73
118
|
$count(nodes[resolvedParentTitle=$$.current.title and resolvedType='solution'])
|
|
74
119
|
```
|
|
75
120
|
|
|
76
|
-
|
|
121
|
+
### Common patterns
|
|
77
122
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
123
|
+
```jsonata
|
|
124
|
+
$exists(current.metric) = true
|
|
125
|
+
$count(current.sources) >= 1
|
|
126
|
+
$count(nodes[resolvedType='outcome' and status='active']) <= 1
|
|
127
|
+
current.status != 'active' or $exists(parent) = false or parent.status = 'active'
|
|
128
|
+
```
|
|
81
129
|
|
|
82
|
-
|
|
130
|
+
#### Dates and times
|
|
83
131
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
132
|
+
Dates and datetimes reach rules as ISO 8601 strings. Convert with `$toMillis()` to compare them, including date against datetime, and use `$millis()` for the current time:
|
|
133
|
+
|
|
134
|
+
```jsonata
|
|
135
|
+
$not($exists(current.due)) or $toMillis(current.due) > $millis()
|
|
136
|
+
$toMillis(current.stale_after) > $toMillis(current.generated.at)
|
|
89
137
|
```
|
|
90
138
|
|
|
91
|
-
|
|
139
|
+
A datetime without an offset (`2026-07-01T00:00:00`) is read in the machine's local time zone, so require an offset with `format: "date-time"` when results must not depend on where validation runs. `$toMillis()` also needs seconds: it cannot parse a minute-precision datetime such as `2026-07-01T09:15`, which `format: "date-or-date-time"` accepts. A value `$toMillis()` cannot parse makes the rule fail.
|
|
140
|
+
|
|
141
|
+
The same applies to [filter expressions](concepts.md#filter-expressions): compare dates with `$toMillis()`, not as strings.
|
|
142
|
+
|
|
143
|
+
## Sharing rules
|
|
92
144
|
|
|
93
|
-
|
|
145
|
+
To share rules, put them in a schema's `$metadata.rules` and list that schema's `$id` in `$metadata.imports`. An import brings the whole schema's metadata, including its own imports. Merged rules combine by `id`; a duplicate `id` is an error, and a later rule replaces an earlier one only with `override: true`.
|
|
94
146
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
147
|
+
### Overriding rules
|
|
148
|
+
|
|
149
|
+
```json5
|
|
150
|
+
"$metadata": {
|
|
151
|
+
"imports": ["sctx://_rule-pack"]
|
|
152
|
+
}
|
|
153
|
+
```
|
|
99
154
|
|
|
100
155
|
Example override:
|
|
101
156
|
|
|
@@ -105,16 +160,79 @@ Example override:
|
|
|
105
160
|
"override": true,
|
|
106
161
|
"category": "workflow",
|
|
107
162
|
"description": "Require exactly one active outcome",
|
|
108
|
-
"
|
|
109
|
-
"
|
|
163
|
+
"global": true,
|
|
164
|
+
"expression": "$count(nodes[resolvedType='outcome' and status='active']) = 1"
|
|
110
165
|
}
|
|
111
166
|
```
|
|
112
167
|
|
|
113
|
-
##
|
|
168
|
+
## Cascade gating
|
|
169
|
+
|
|
170
|
+
When a schema uses `$metadata.hierarchy`, rule evaluation cascades top-down through the
|
|
171
|
+
hierarchy tree (breadth-first from roots and orphans). A **gate** occurs when a node fails
|
|
172
|
+
in a way that makes its descendants' rules untrustworthy or wasteful to run:
|
|
173
|
+
|
|
174
|
+
- **Schema validation failure** on a node — always gates. The node's own rules are
|
|
175
|
+
suppressed too, since its data is structurally invalid.
|
|
176
|
+
- **Hierarchy structure violation** on a node (e.g. an invalid parent) — always gates the
|
|
177
|
+
node's subtree, though the node's own rules still run.
|
|
178
|
+
- **A rule failure with `gatesDescendants: true`** — gates the node's subtree, though the
|
|
179
|
+
failing node's own other rules still run.
|
|
180
|
+
|
|
181
|
+
Gating exists so that expensive or semantic downstream rules (see [Semantic rules](semantic-rules.md)) aren't wasted
|
|
182
|
+
evaluating nodes whose ancestors are already known-bad, and so noise doesn't drown out the
|
|
183
|
+
actionable failure. It is per-subtree: once a node is gated, its descendants inherit the same
|
|
184
|
+
gate (the original blocker), rather than each generating a new one. Cascade gating only applies to nodes within the
|
|
185
|
+
hierarchy DAG.
|
|
186
|
+
|
|
187
|
+
A rule has one `gatesDescendants` setting. It applies to whichever part failed, the `expression` or the `semantic` part. Its default comes from the severity of that failure: `true` for `error`, and `false` for `warning` and `info`. Set it explicitly when that default doesn't fit.
|
|
188
|
+
|
|
189
|
+
### Coverage output
|
|
190
|
+
|
|
191
|
+
`sctx validate` reports gating as **coverage gates**: which node's failure (schema,
|
|
192
|
+
hierarchy, or a specific `ruleId`) suppressed rule evaluation, and the list of files gated
|
|
193
|
+
as a result. This makes the validation "edge" visible — e.g. "this blocked goal means all
|
|
194
|
+
opportunities beneath it were not evaluated" — for both human reading and agent consumption
|
|
195
|
+
(JSON output via `--json`, under `coverageGates`).
|
|
196
|
+
|
|
197
|
+
Use `--include-gated` to evaluate rules on gated nodes anyway (still reported as gated) —
|
|
198
|
+
useful for surfacing cheap, bottom-up checks (e.g. orphan detection, content quality) that
|
|
199
|
+
don't depend on an ancestor being valid.
|
|
200
|
+
|
|
201
|
+
### Single-file validation
|
|
202
|
+
|
|
203
|
+
`sctx validate-file <path>` applies the same gating as `sctx validate`, so a file's rule results match its entry in a full run.
|
|
204
|
+
|
|
205
|
+
- Expressions are evaluated for the whole space; the output is restricted to the file.
|
|
206
|
+
- Semantic parts are evaluated only for the file and its gating ancestors (see [Semantic rules](semantic-rules.md#how-semantic-evaluation-works)).
|
|
207
|
+
- If the file is gated, its rules are skipped and a `gated:<file>` entry under `warnings` names the blocker, so a gated file doesn't read as clean. `--include-gated` evaluates the file's rules anyway.
|
|
208
|
+
|
|
209
|
+
## Controlling validation runs
|
|
210
|
+
|
|
211
|
+
These options restrict which rules run, without changing rule definitions.
|
|
212
|
+
|
|
213
|
+
### Filtering rules
|
|
214
|
+
|
|
215
|
+
`--rule-filter <dimension>=<value>[,<value>]` runs only the rules that match. It works on `sctx validate` and `sctx validate-file`.
|
|
216
|
+
|
|
217
|
+
- Dimensions: `category`, `severity`, `type` and `id`.
|
|
218
|
+
- Values in one dimension combine with OR. Repeat the option to add a dimension; dimensions combine with AND.
|
|
219
|
+
- A rule matches each severity that it can report, including the severities in a semantic verdict map.
|
|
220
|
+
- Gating comes only from the rules that ran.
|
|
221
|
+
|
|
222
|
+
For example, `--rule-filter category=workflow,coherence --rule-filter severity=error` runs only workflow and coherence rules that can report an error.
|
|
223
|
+
|
|
224
|
+
### Depth-limited evaluation
|
|
225
|
+
|
|
226
|
+
`--depth <n>` restricts validation to hierarchy nodes within `n` breadth-first levels from
|
|
227
|
+
the top (roots and orphans are depth 1). This is a manual, coarser control than cascade
|
|
228
|
+
gating — intended for staged workflows (e.g. an agent fixes the top level, then requests
|
|
229
|
+
the next). Excluded nodes are reported under `skippedByDepth`, separate from gated nodes.
|
|
230
|
+
|
|
231
|
+
## Output
|
|
232
|
+
|
|
233
|
+
- **`sctx validate`** — the summary counts rule violations per severity, and details are grouped by severity with each line showing the rule's category and id.
|
|
234
|
+
- **`sctx validate --json`** — `errors` includes only `error` severity rule violations (keyed `rule:<id>`, with a `severity` field). A `ruleViolations` array lists every violation with `file`, `ruleId`, `category`, `severity` and `description` (`file` is empty for global rules). A violation from a failed `expression` has the evidence `expression failed: <expression>` when the rule also has a `semantic` part, and no verdict.
|
|
235
|
+
- **`sctx validate-file`** — `error` severity violations appear under `errors`; `warning` and `info` violations appear under `warnings`. Rule entries carry a `severity` field.
|
|
236
|
+
|
|
237
|
+
Gated files: see [Single-file validation](#single-file-validation).
|
|
114
238
|
|
|
115
|
-
```jsonata
|
|
116
|
-
$exists(current.metric) = true
|
|
117
|
-
$count(current.sources) >= 1
|
|
118
|
-
$count(nodes[resolvedType='outcome' and status='active']) <= 1
|
|
119
|
-
current.status != 'active' or $exists(parent) = false or parent.status = 'active'
|
|
120
|
-
```
|