aontu 0.52.1 → 0.53.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 +88 -0
- package/bin/aontu-mcp.js +4 -0
- package/dist/agentsmd.d.ts +16 -0
- package/dist/agentsmd.js +107 -0
- package/dist/agentsmd.js.map +1 -0
- package/dist/aontu.d.ts +14 -3
- package/dist/aontu.js +96 -4
- package/dist/aontu.js.map +1 -1
- package/dist/cli.d.ts +44 -1
- package/dist/cli.js +2401 -44
- package/dist/cli.js.map +1 -1
- package/dist/ctx.d.ts +13 -0
- package/dist/ctx.js +43 -0
- package/dist/ctx.js.map +1 -1
- package/dist/diff.d.ts +22 -0
- package/dist/diff.js +141 -0
- package/dist/diff.js.map +1 -0
- package/dist/err.d.ts +3 -1
- package/dist/err.js +38 -7
- package/dist/err.js.map +1 -1
- package/dist/graph.d.ts +16 -0
- package/dist/graph.js +73 -0
- package/dist/graph.js.map +1 -0
- package/dist/hcanon.d.ts +3 -0
- package/dist/hcanon.js +146 -0
- package/dist/hcanon.js.map +1 -0
- package/dist/hints.js +167 -4
- package/dist/hints.js.map +1 -1
- package/dist/jsonschema.d.ts +20 -0
- package/dist/jsonschema.js +391 -0
- package/dist/jsonschema.js.map +1 -0
- package/dist/lang.js +512 -69
- package/dist/lang.js.map +1 -1
- package/dist/lsp.d.ts +9 -2
- package/dist/lsp.js +262 -47
- package/dist/lsp.js.map +1 -1
- package/dist/mcp-server.d.ts +20 -0
- package/dist/mcp-server.js +147 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/mcp.d.ts +42 -0
- package/dist/mcp.js +814 -0
- package/dist/mcp.js.map +1 -0
- package/dist/mod-tool.d.ts +58 -0
- package/dist/mod-tool.js +498 -0
- package/dist/mod-tool.js.map +1 -0
- package/dist/mod.d.ts +31 -0
- package/dist/mod.js +250 -0
- package/dist/mod.js.map +1 -0
- package/dist/patch.d.ts +44 -0
- package/dist/patch.js +506 -0
- package/dist/patch.js.map +1 -0
- package/dist/provenance.d.ts +40 -0
- package/dist/provenance.js +335 -0
- package/dist/provenance.js.map +1 -0
- package/dist/query.d.ts +27 -0
- package/dist/query.js +294 -0
- package/dist/query.js.map +1 -0
- package/dist/reach.d.ts +14 -0
- package/dist/reach.js +140 -0
- package/dist/reach.js.map +1 -0
- package/dist/relation.d.ts +19 -0
- package/dist/relation.js +305 -0
- package/dist/relation.js.map +1 -0
- package/dist/report-sarif.d.ts +14 -0
- package/dist/report-sarif.js +102 -0
- package/dist/report-sarif.js.map +1 -0
- package/dist/site.d.ts +4 -0
- package/dist/site.js +31 -0
- package/dist/site.js.map +1 -1
- package/dist/std.d.ts +1 -0
- package/dist/std.js +73 -0
- package/dist/std.js.map +1 -0
- package/dist/subsume.d.ts +39 -0
- package/dist/subsume.js +526 -0
- package/dist/subsume.js.map +1 -0
- package/dist/trim.d.ts +19 -0
- package/dist/trim.js +155 -0
- package/dist/trim.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/type.d.ts +17 -1
- package/dist/type.js.map +1 -1
- package/dist/unify.d.ts +2 -1
- package/dist/unify.js +253 -36
- package/dist/unify.js.map +1 -1
- package/dist/utility.d.ts +9 -1
- package/dist/utility.js +122 -1
- package/dist/utility.js.map +1 -1
- package/dist/val/AggFuncVal.d.ts +33 -0
- package/dist/val/AggFuncVal.js +202 -0
- package/dist/val/AggFuncVal.js.map +1 -0
- package/dist/val/ArithFuncVal.d.ts +31 -0
- package/dist/val/ArithFuncVal.js +62 -0
- package/dist/val/ArithFuncVal.js.map +1 -0
- package/dist/val/BagVal.d.ts +5 -0
- package/dist/val/BagVal.js +96 -5
- package/dist/val/BagVal.js.map +1 -1
- package/dist/val/CloseFuncVal.js +9 -1
- package/dist/val/CloseFuncVal.js.map +1 -1
- package/dist/val/ConjunctVal.d.ts +1 -1
- package/dist/val/ConjunctVal.js +19 -0
- package/dist/val/ConjunctVal.js.map +1 -1
- package/dist/val/ConstraintVal.d.ts +7 -1
- package/dist/val/ConstraintVal.js +490 -40
- package/dist/val/ConstraintVal.js.map +1 -1
- package/dist/val/CopyFuncVal.d.ts +1 -2
- package/dist/val/CopyFuncVal.js +7 -0
- package/dist/val/CopyFuncVal.js.map +1 -1
- package/dist/val/Decimal.d.ts +1 -0
- package/dist/val/Decimal.js +13 -0
- package/dist/val/Decimal.js.map +1 -1
- package/dist/val/DeprecateFuncVal.d.ts +11 -0
- package/dist/val/DeprecateFuncVal.js +47 -0
- package/dist/val/DeprecateFuncVal.js.map +1 -0
- package/dist/val/DisjunctVal.js +130 -21
- package/dist/val/DisjunctVal.js.map +1 -1
- package/dist/val/EachFuncVal.d.ts +15 -0
- package/dist/val/EachFuncVal.js +75 -0
- package/dist/val/EachFuncVal.js.map +1 -0
- package/dist/val/ExpectVal.js +27 -4
- package/dist/val/ExpectVal.js.map +1 -1
- package/dist/val/FeatureVal.js +1 -1
- package/dist/val/FeatureVal.js.map +1 -1
- package/dist/val/FilterFuncVal.d.ts +15 -0
- package/dist/val/FilterFuncVal.js +91 -0
- package/dist/val/FilterFuncVal.js.map +1 -0
- package/dist/val/FuncBaseVal.d.ts +6 -1
- package/dist/val/FuncBaseVal.js +171 -1
- package/dist/val/FuncBaseVal.js.map +1 -1
- package/dist/val/HideFuncVal.js.map +1 -1
- package/dist/val/IdFuncVal.d.ts +13 -0
- package/dist/val/IdFuncVal.js +54 -0
- package/dist/val/IdFuncVal.js.map +1 -0
- package/dist/val/JunctionVal.js +7 -1
- package/dist/val/JunctionVal.js.map +1 -1
- package/dist/val/KeyFuncVal.d.ts +1 -1
- package/dist/val/KeyFuncVal.js +38 -30
- package/dist/val/KeyFuncVal.js.map +1 -1
- package/dist/val/ListVal.js +101 -14
- package/dist/val/ListVal.js.map +1 -1
- package/dist/val/LowerFuncVal.js.map +1 -1
- package/dist/val/MapVal.js +93 -8
- package/dist/val/MapVal.js.map +1 -1
- package/dist/val/MatchFuncVal.d.ts +15 -0
- package/dist/val/MatchFuncVal.js +107 -0
- package/dist/val/MatchFuncVal.js.map +1 -0
- package/dist/val/MoveFuncVal.js.map +1 -1
- package/dist/val/NilVal.js +24 -0
- package/dist/val/NilVal.js.map +1 -1
- package/dist/val/OpBaseVal.d.ts +1 -1
- package/dist/val/OpBaseVal.js +19 -1
- package/dist/val/OpBaseVal.js.map +1 -1
- package/dist/val/OpenFuncVal.js +4 -1
- package/dist/val/OpenFuncVal.js.map +1 -1
- package/dist/val/PackFuncVal.d.ts +15 -0
- package/dist/val/PackFuncVal.js +108 -0
- package/dist/val/PackFuncVal.js.map +1 -0
- package/dist/val/PathFuncVal.js.map +1 -1
- package/dist/val/PlaceVal.d.ts +13 -0
- package/dist/val/PlaceVal.js +131 -0
- package/dist/val/PlaceVal.js.map +1 -0
- package/dist/val/PlusOpVal.js +11 -2
- package/dist/val/PlusOpVal.js.map +1 -1
- package/dist/val/PrefFuncVal.js.map +1 -1
- package/dist/val/PrefVal.d.ts +2 -2
- package/dist/val/PrefVal.js +78 -23
- package/dist/val/PrefVal.js.map +1 -1
- package/dist/val/RefVal.d.ts +1 -1
- package/dist/val/RefVal.js +89 -7
- package/dist/val/RefVal.js.map +1 -1
- package/dist/val/ReferFuncVal.d.ts +36 -0
- package/dist/val/ReferFuncVal.js +303 -0
- package/dist/val/ReferFuncVal.js.map +1 -0
- package/dist/val/ScalarKindVal.d.ts +1 -2
- package/dist/val/ScalarKindVal.js +0 -11
- package/dist/val/ScalarKindVal.js.map +1 -1
- package/dist/val/TopVal.js.map +1 -1
- package/dist/val/TypeFuncVal.js.map +1 -1
- package/dist/val/UpperFuncVal.js.map +1 -1
- package/dist/val/Val.d.ts +8 -1
- package/dist/val/Val.js +150 -1
- package/dist/val/Val.js.map +1 -1
- package/dist/val/VarVal.js.map +1 -1
- package/dist/val/arith.d.ts +6 -0
- package/dist/val/arith.js +170 -0
- package/dist/val/arith.js.map +1 -0
- package/dist/vet.d.ts +45 -0
- package/dist/vet.js +776 -0
- package/dist/vet.js.map +1 -0
- package/dist/walk.d.ts +2 -0
- package/dist/walk.js +91 -0
- package/dist/walk.js.map +1 -0
- package/grammar/aontu.gbnf +130 -0
- package/grammar/aontu.lark +113 -0
- package/package.json +21 -6
- package/skill/SKILL.md +37 -0
- package/skill/error-codes.md +62 -0
- package/skill/examples.md +99 -0
- package/skill/grammar-card.md +57 -0
- package/src/agentsmd.ts +135 -0
- package/src/aontu.ts +135 -4
- package/src/cli.ts +2858 -71
- package/src/ctx.ts +73 -1
- package/src/diff.ts +196 -0
- package/src/err.ts +42 -7
- package/src/graph.ts +135 -0
- package/src/hcanon.ts +169 -0
- package/src/hints.ts +208 -4
- package/src/jsonschema.ts +511 -0
- package/src/lang.ts +570 -70
- package/src/lsp.ts +281 -48
- package/src/mcp-server.ts +187 -0
- package/src/mcp.ts +993 -0
- package/src/mod-tool.ts +679 -0
- package/src/mod.ts +344 -0
- package/src/patch.ts +624 -0
- package/src/provenance.ts +430 -0
- package/src/query.ts +379 -0
- package/src/reach.ts +184 -0
- package/src/relation.ts +395 -0
- package/src/report-sarif.ts +137 -0
- package/src/site.ts +36 -1
- package/src/std.ts +73 -0
- package/src/subsume.ts +690 -0
- package/src/trim.ts +195 -0
- package/src/tsconfig.json +10 -4
- package/src/type.ts +51 -2
- package/src/unify.ts +274 -36
- package/src/utility.ts +139 -1
- package/src/val/AggFuncVal.ts +319 -0
- package/src/val/ArithFuncVal.ts +108 -0
- package/src/val/BagVal.ts +101 -4
- package/src/val/CloseFuncVal.ts +9 -1
- package/src/val/ConjunctVal.ts +20 -0
- package/src/val/ConstraintVal.ts +542 -43
- package/src/val/CopyFuncVal.ts +7 -1
- package/src/val/Decimal.ts +15 -0
- package/src/val/DeprecateFuncVal.ts +84 -0
- package/src/val/DisjunctVal.ts +139 -28
- package/src/val/EachFuncVal.ts +133 -0
- package/src/val/ExpectVal.ts +28 -6
- package/src/val/FeatureVal.ts +1 -1
- package/src/val/FilterFuncVal.ts +154 -0
- package/src/val/FuncBaseVal.ts +192 -1
- package/src/val/HideFuncVal.ts +0 -2
- package/src/val/IdFuncVal.ts +91 -0
- package/src/val/JunctionVal.ts +7 -1
- package/src/val/KeyFuncVal.ts +39 -35
- package/src/val/ListVal.ts +108 -15
- package/src/val/LowerFuncVal.ts +0 -1
- package/src/val/MapVal.ts +99 -8
- package/src/val/MatchFuncVal.ts +176 -0
- package/src/val/MoveFuncVal.ts +0 -2
- package/src/val/NilVal.ts +25 -0
- package/src/val/OpBaseVal.ts +20 -1
- package/src/val/OpenFuncVal.ts +4 -2
- package/src/val/PackFuncVal.ts +175 -0
- package/src/val/PathFuncVal.ts +0 -1
- package/src/val/PlaceVal.ts +193 -0
- package/src/val/PlusOpVal.ts +11 -2
- package/src/val/PrefFuncVal.ts +0 -1
- package/src/val/PrefVal.ts +79 -36
- package/src/val/RefVal.ts +91 -8
- package/src/val/ReferFuncVal.ts +387 -0
- package/src/val/ScalarKindVal.ts +0 -13
- package/src/val/TopVal.ts +0 -1
- package/src/val/TypeFuncVal.ts +0 -2
- package/src/val/UpperFuncVal.ts +0 -1
- package/src/val/Val.ts +205 -2
- package/src/val/VarVal.ts +0 -1
- package/src/val/arith.ts +316 -0
- package/src/vet.ts +992 -0
- package/src/walk.ts +99 -0
package/src/relation.ts
ADDED
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
/* Copyright (c) 2025 Richard Rodger, MIT License */
|
|
2
|
+
|
|
3
|
+
// RELATION GRAPH CHECKS (G4 phase 5,
|
|
4
|
+
// docs/capability-review/g4-identity-relations.md): acyclicity and
|
|
5
|
+
// inverse consistency over the edge set, checked AFTER unification and
|
|
6
|
+
// never by it.
|
|
7
|
+
//
|
|
8
|
+
// Why not in the lattice. Both properties are GLOBAL and NON-MONOTONE:
|
|
9
|
+
// an acyclic graph becomes cyclic when one more edge unifies in, and an
|
|
10
|
+
// inverse that is present becomes absent when the far side is narrowed.
|
|
11
|
+
// The lattice guarantee is that more information never falsifies what
|
|
12
|
+
// has been observed, so a constraint that could be true and then false
|
|
13
|
+
// is not a constraint the lattice may hold. These are facts about the
|
|
14
|
+
// finished model, and the verb that reports facts about a finished
|
|
15
|
+
// model is where they belong.
|
|
16
|
+
//
|
|
17
|
+
// A relation is DECLARED as data, under the `relations` key of the
|
|
18
|
+
// document root, which is the `std/system` vocabulary's convention:
|
|
19
|
+
//
|
|
20
|
+
// relations: dependsOn: $.std.Relation & {
|
|
21
|
+
// target: $.std.Service, inverse: dependedOnBy, acyclic: true
|
|
22
|
+
// }
|
|
23
|
+
//
|
|
24
|
+
// Nothing in the engine knows the name `relations`; this pass does,
|
|
25
|
+
// and says so.
|
|
26
|
+
|
|
27
|
+
import { Aontu } from './aontu'
|
|
28
|
+
import { failureFinding } from './vet'
|
|
29
|
+
import type { VetFinding } from './vet'
|
|
30
|
+
import type { TrustOptions, Val } from './type'
|
|
31
|
+
import { graphOf } from './graph'
|
|
32
|
+
import type { Edge, EntityEntry, Graph } from './graph'
|
|
33
|
+
import { cmpCodePoint } from './keyorder'
|
|
34
|
+
import { unite } from './unify'
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
export type RelationVerdict = 'pass' | 'fail' | 'error'
|
|
38
|
+
|
|
39
|
+
export type RelationFinding = {
|
|
40
|
+
code: string
|
|
41
|
+
// The relation the finding is about.
|
|
42
|
+
relation: string
|
|
43
|
+
// Where the offending edge is written, as a `$.dotted.path`.
|
|
44
|
+
at: string
|
|
45
|
+
// For a cycle, the entities it runs through, in the order the walk
|
|
46
|
+
// found them, closing back on the first. For a missing inverse, the
|
|
47
|
+
// two ends and the relation that should have mirrored it.
|
|
48
|
+
detail: string[]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type RelationReport = {
|
|
52
|
+
verdict: RelationVerdict
|
|
53
|
+
findings: RelationFinding[]
|
|
54
|
+
|
|
55
|
+
// WHY the graph could not be looked at, in the same finding shape
|
|
56
|
+
// vet reports in (the review's finding F). `findings` is about the
|
|
57
|
+
// GRAPH and stays that way; a document that does not stand up has no
|
|
58
|
+
// graph to have findings about, and an `error` verdict used to
|
|
59
|
+
// arrive with an empty list -- something is wrong, and nothing about
|
|
60
|
+
// what. Present ONLY on an `error` verdict.
|
|
61
|
+
errors?: VetFinding[]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type RelationOptions = {
|
|
65
|
+
// Where the document CAME FROM, so a relative `@"file"` load inside
|
|
66
|
+
// it resolves from its own directory (trimCheck's precedent).
|
|
67
|
+
path?: string
|
|
68
|
+
// The include capability this document evaluates under (G5,
|
|
69
|
+
// docs/trust.md). vet's precedent: the verb passes the profile the
|
|
70
|
+
// caller asked for, and an absent option means today's default.
|
|
71
|
+
trust?: TrustOptions
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
// One declared relation, as the document spells it.
|
|
76
|
+
type Declared = {
|
|
77
|
+
name: string
|
|
78
|
+
inverse?: string
|
|
79
|
+
acyclic: boolean
|
|
80
|
+
// What the FAR END must satisfy, if the relation says. Absent when
|
|
81
|
+
// the relation declares none, and when it declares `top` -- which
|
|
82
|
+
// constrains nothing and would report nothing, so reading it as a
|
|
83
|
+
// declaration would only cost a meet per edge.
|
|
84
|
+
target?: Val
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
// The entity an address names — everything before the first dot. An
|
|
89
|
+
// edge into `svc/auth.ports.http` is an edge to `svc/auth`: a relation
|
|
90
|
+
// holds between ENTITIES, and the path inside one says which part of it
|
|
91
|
+
// the link reaches.
|
|
92
|
+
function entityOf(addr: string): string {
|
|
93
|
+
const dot = addr.indexOf('.')
|
|
94
|
+
return dot < 0 ? addr : addr.slice(0, dot)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
// The node an address names, or undefined. The entity's own position
|
|
99
|
+
// comes from the graph (a merged entity sits at every position that
|
|
100
|
+
// declared it, and they hold the same value, so the FIRST in the
|
|
101
|
+
// graph's sorted list is as good as any and is the same one in both
|
|
102
|
+
// ports); the rest of the address walks into it, exactly as the
|
|
103
|
+
// address's own grammar says.
|
|
104
|
+
// NO GUARD ON THE LOOKUP OR THE WALK. An edge exists only because
|
|
105
|
+
// `refer()` RESOLVED its full address, so `find` cannot miss and no
|
|
106
|
+
// segment can fall off: an address that does not walk is
|
|
107
|
+
// `refer_unresolved` at unification and the document never reaches the
|
|
108
|
+
// graph (probed for a missing key, a scalar mid-path and an
|
|
109
|
+
// out-of-range index, in both ports). An unreachable `if` is a branch
|
|
110
|
+
// arm the ADR-002 gate counts and no marker suppresses, so the Go twin
|
|
111
|
+
// keeps its guards — where a nil would PANIC rather than propagate,
|
|
112
|
+
// and where the marker mechanism can carry them — and this one relies
|
|
113
|
+
// on optional chaining instead.
|
|
114
|
+
function addressed(
|
|
115
|
+
root: any, graph: Graph, addr: string
|
|
116
|
+
): any {
|
|
117
|
+
const dot = addr.indexOf('.')
|
|
118
|
+
const name = dot < 0 ? addr : addr.slice(0, dot)
|
|
119
|
+
const entry = graph.entities.find((e) => e.id === name) as EntityEntry
|
|
120
|
+
const segs = entry.paths[0].slice(2).split('.')
|
|
121
|
+
.concat(dot < 0 ? [] : addr.slice(dot + 1).split('.'))
|
|
122
|
+
let node: any = root
|
|
123
|
+
for (const seg of segs) {
|
|
124
|
+
node = node?.peg?.[seg]
|
|
125
|
+
}
|
|
126
|
+
return node
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
// Does the far end satisfy the declared target? A TEST, never a flow:
|
|
131
|
+
// the check reports on a finished model and writing into it would be
|
|
132
|
+
// generation, which `relations` does not do (the same rule that keeps
|
|
133
|
+
// it from writing an author's inverse for them). So both sides are
|
|
134
|
+
// CLONED into a throwaway context and the meet is taken there; what
|
|
135
|
+
// the document holds is untouched either way.
|
|
136
|
+
//
|
|
137
|
+
// `refer(t)` is the other half of this and does flow, at the site. The
|
|
138
|
+
// two agree on what "satisfies" means -- a meet that is not a nil --
|
|
139
|
+
// which is what lets a relation declare once what every site would
|
|
140
|
+
// otherwise repeat.
|
|
141
|
+
// `root` is the DOCUMENT, not the node: a target lifted out of the
|
|
142
|
+
// model (`target: $.std.Service`) can still hold a reference that
|
|
143
|
+
// resolves against the document, and a probe rooted at the far end
|
|
144
|
+
// answers `no_path` for it -- which the worked example in
|
|
145
|
+
// test/spec/relation.tsv caught the moment this check existed.
|
|
146
|
+
function meets(
|
|
147
|
+
aontu: any, root: any, node: any, target: any
|
|
148
|
+
): string | undefined {
|
|
149
|
+
const ctx = aontu.ctx({ collect: true })
|
|
150
|
+
ctx.root = root
|
|
151
|
+
const out: any = unite(ctx, node.clone(ctx), target.clone(ctx), 'relation-target')
|
|
152
|
+
if (true === out?.isNil) {
|
|
153
|
+
return out.why as string
|
|
154
|
+
}
|
|
155
|
+
if (0 < ctx.err.length) {
|
|
156
|
+
return ctx.err[0].why as string
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// A MEET THAT LEAVES A HOLE IS NOT SATISFACTION. `target:
|
|
160
|
+
// {kind: service, port: integer}` against a far end with no `port`
|
|
161
|
+
// does not CONFLICT -- the meet simply carries `integer` into a key
|
|
162
|
+
// that had none -- and a check that stopped at "no conflict" would
|
|
163
|
+
// pass a far end that is missing half of what the relation demands.
|
|
164
|
+
//
|
|
165
|
+
// What `refer(t)` does at the site is the yardstick: it flows `t` in,
|
|
166
|
+
// and the document then fails to generate, because `integer` is not a
|
|
167
|
+
// value. So the same question is asked here -- can the far end still
|
|
168
|
+
// generate once the target is met? -- and the answer is compared with
|
|
169
|
+
// the far end ALONE, so a node that was already incomplete for its
|
|
170
|
+
// own reasons is not blamed on the relation that points at it.
|
|
171
|
+
// The REASON reported is the engine's own -- the code `refer(t)` at
|
|
172
|
+
// the site would have raised -- rather than a name invented here.
|
|
173
|
+
const probe = (v: any): string | undefined => {
|
|
174
|
+
const gctx = aontu.ctx({ collect: true })
|
|
175
|
+
gctx.root = root
|
|
176
|
+
v.gen(gctx)
|
|
177
|
+
return 0 === gctx.err.length ? undefined : (gctx.err[0].why as string)
|
|
178
|
+
}
|
|
179
|
+
const alone = probe(node.clone(aontu.ctx({ collect: true })))
|
|
180
|
+
const met = probe(out)
|
|
181
|
+
return undefined === alone && undefined !== met ? met : undefined
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
function declaredRelations(root: any): Declared[] {
|
|
186
|
+
const rels = root?.peg?.relations
|
|
187
|
+
if (true !== rels?.isMap) {
|
|
188
|
+
return []
|
|
189
|
+
}
|
|
190
|
+
const out: Declared[] = []
|
|
191
|
+
for (const name of Object.keys(rels.peg).sort(cmpCodePoint)) {
|
|
192
|
+
const r: any = rels.peg[name]
|
|
193
|
+
if (true !== r?.isMap) {
|
|
194
|
+
continue
|
|
195
|
+
}
|
|
196
|
+
const inv: any = r.peg.inverse
|
|
197
|
+
const acy: any = r.peg.acyclic
|
|
198
|
+
const tgt: any = r.peg.target
|
|
199
|
+
out.push({
|
|
200
|
+
name,
|
|
201
|
+
inverse: true === inv?.isScalar && 'string' === typeof inv.peg
|
|
202
|
+
? inv.peg : undefined,
|
|
203
|
+
acyclic: true === acy?.isScalar && true === acy.peg,
|
|
204
|
+
target: true === tgt?.isVal && true !== tgt.isTop ? tgt : undefined,
|
|
205
|
+
})
|
|
206
|
+
}
|
|
207
|
+
return out
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
// The first cycle reachable from `start`, as the entities it runs
|
|
212
|
+
// through, or undefined. Depth-first with the path as the stack, and
|
|
213
|
+
// the successors visited in sorted order, so the cycle a report names
|
|
214
|
+
// is the same one in both ports.
|
|
215
|
+
function findCycle(
|
|
216
|
+
start: string,
|
|
217
|
+
succ: Map<string, string[]>,
|
|
218
|
+
done: Set<string>,
|
|
219
|
+
): string[] | undefined {
|
|
220
|
+
const stack: string[] = []
|
|
221
|
+
const onStack = new Set<string>()
|
|
222
|
+
|
|
223
|
+
const walk = (node: string): string[] | undefined => {
|
|
224
|
+
if (onStack.has(node)) {
|
|
225
|
+
return [...stack.slice(stack.indexOf(node)), node]
|
|
226
|
+
}
|
|
227
|
+
if (done.has(node)) {
|
|
228
|
+
return undefined
|
|
229
|
+
}
|
|
230
|
+
done.add(node)
|
|
231
|
+
stack.push(node)
|
|
232
|
+
onStack.add(node)
|
|
233
|
+
for (const next of succ.get(node) ?? []) {
|
|
234
|
+
const found = walk(next)
|
|
235
|
+
if (undefined !== found) {
|
|
236
|
+
return found
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
stack.pop()
|
|
240
|
+
onStack.delete(node)
|
|
241
|
+
return undefined
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return walk(start)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
// The relation checks for one document.
|
|
249
|
+
export function relationCheck(
|
|
250
|
+
src: string, opts?: RelationOptions): RelationReport {
|
|
251
|
+
const options = opts ?? {}
|
|
252
|
+
const aontu = new Aontu(
|
|
253
|
+
null == options.trust ? undefined : { trust: options.trust })
|
|
254
|
+
const ctx = aontu.ctx({ collect: true })
|
|
255
|
+
const parseOpts = null == options.path ? undefined : { path: options.path }
|
|
256
|
+
const root: any = aontu.unify(src, parseOpts, ctx)
|
|
257
|
+
|
|
258
|
+
// A document that does not stand up is not a document with a bad
|
|
259
|
+
// graph: the errors it already has are the answer, and blaming its
|
|
260
|
+
// relations on top would be noise.
|
|
261
|
+
if (0 < ctx.err.length || true === root?.isNil) {
|
|
262
|
+
return {
|
|
263
|
+
verdict: 'error',
|
|
264
|
+
findings: [],
|
|
265
|
+
errors: [failureFinding(ctx, options.path, root)],
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const declared = declaredRelations(root)
|
|
270
|
+
if (0 === declared.length) {
|
|
271
|
+
return { verdict: 'pass', findings: [] }
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const graph = graphOf(root)
|
|
275
|
+
const edges = graph.edges
|
|
276
|
+
const findings: RelationFinding[] = []
|
|
277
|
+
|
|
278
|
+
// The edge set, indexed the two ways the checks read it.
|
|
279
|
+
const byRelation = new Map<string, Edge[]>()
|
|
280
|
+
const pairs = new Set<string>()
|
|
281
|
+
for (const e of edges) {
|
|
282
|
+
if ('' === e.from) {
|
|
283
|
+
// An edge outside every entity has no source to be a relation OF.
|
|
284
|
+
continue
|
|
285
|
+
}
|
|
286
|
+
const list = byRelation.get(e.key)
|
|
287
|
+
if (undefined === list) {
|
|
288
|
+
byRelation.set(e.key, [e])
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
list.push(e)
|
|
292
|
+
}
|
|
293
|
+
pairs.add(e.key + ' ' + e.from + ' ' + entityOf(e.to))
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
for (const rel of declared) {
|
|
297
|
+
const mine = byRelation.get(rel.name) ?? []
|
|
298
|
+
|
|
299
|
+
if (rel.acyclic) {
|
|
300
|
+
const succ = new Map<string, string[]>()
|
|
301
|
+
for (const e of mine) {
|
|
302
|
+
const list = succ.get(e.from)
|
|
303
|
+
const to = entityOf(e.to)
|
|
304
|
+
if (undefined === list) {
|
|
305
|
+
succ.set(e.from, [to])
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
list.push(to)
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
for (const list of succ.values()) {
|
|
312
|
+
list.sort(cmpCodePoint)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// The roots are visited in sorted order, and a node already
|
|
316
|
+
// settled is not revisited, so one cycle is reported once and the
|
|
317
|
+
// SAME one in both ports.
|
|
318
|
+
const done = new Set<string>()
|
|
319
|
+
const roots = [...succ.keys()].sort(cmpCodePoint)
|
|
320
|
+
for (const from of roots) {
|
|
321
|
+
const cycle = findCycle(from, succ, done)
|
|
322
|
+
if (undefined !== cycle) {
|
|
323
|
+
// The cycle's first node is a key of `succ`, and every key of
|
|
324
|
+
// `succ` came from an edge's `from`, so the edge is there.
|
|
325
|
+
const at = mine.find((e) => e.from === cycle[0]) as Edge
|
|
326
|
+
findings.push({
|
|
327
|
+
code: 'relation_cycle',
|
|
328
|
+
relation: rel.name,
|
|
329
|
+
at: at.at,
|
|
330
|
+
detail: cycle,
|
|
331
|
+
})
|
|
332
|
+
break
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// TARGET: the far end IS what the relation says it is (the review's
|
|
338
|
+
// finding J). The declaration used to be inert -- `target` was read
|
|
339
|
+
// by nothing, on the reasoning that `refer(t)` already flows the
|
|
340
|
+
// type in at the site. It does, and that is exactly why the
|
|
341
|
+
// declaration was worth nothing: the site has to REPEAT it, and the
|
|
342
|
+
// idiom that avoids repeating it (`refer($.std.Service)`) tripped
|
|
343
|
+
// the fixpoint until §19 was fixed, so every real model wrote bare
|
|
344
|
+
// `refer()` and a typed-endpoint declaration checked nothing.
|
|
345
|
+
//
|
|
346
|
+
// Reported per EDGE, not per entity: an entity reached by two
|
|
347
|
+
// relations must satisfy both, and the report points at the link
|
|
348
|
+
// that made the demand.
|
|
349
|
+
if (undefined !== rel.target) {
|
|
350
|
+
for (const e of mine) {
|
|
351
|
+
// No guard on the node either, and for the same reason
|
|
352
|
+
// `addressed` needs none: an unresolved link is a nil in the
|
|
353
|
+
// tree, so this document would not have reached the graph.
|
|
354
|
+
const why = meets(aontu, root, addressed(root, graph, e.to),
|
|
355
|
+
rel.target)
|
|
356
|
+
if (undefined !== why) {
|
|
357
|
+
findings.push({
|
|
358
|
+
code: 'relation_target_unmet',
|
|
359
|
+
relation: rel.name,
|
|
360
|
+
at: e.at,
|
|
361
|
+
detail: [e.from, e.to, why],
|
|
362
|
+
})
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (undefined !== rel.inverse) {
|
|
368
|
+
for (const e of mine) {
|
|
369
|
+
const to = entityOf(e.to)
|
|
370
|
+
if (!pairs.has(rel.inverse + ' ' + to + ' ' + e.from)) {
|
|
371
|
+
findings.push({
|
|
372
|
+
code: 'relation_inverse_missing',
|
|
373
|
+
relation: rel.name,
|
|
374
|
+
at: e.at,
|
|
375
|
+
detail: [e.from, to, rel.inverse],
|
|
376
|
+
})
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// SORTED, because a report is read by a machine that diffs it: by the
|
|
383
|
+
// position the offending edge is written at, then by code. No third
|
|
384
|
+
// key: one edge sits under one key and one key is one relation, so
|
|
385
|
+
// two findings can share (at, code) only by being the same finding.
|
|
386
|
+
// The sort is STABLE, and the relations were iterated in sorted
|
|
387
|
+
// order, so what order remains is fixed anyway.
|
|
388
|
+
findings.sort((a, b) =>
|
|
389
|
+
cmpCodePoint(a.at, b.at) || cmpCodePoint(a.code, b.code))
|
|
390
|
+
|
|
391
|
+
return {
|
|
392
|
+
verdict: 0 === findings.length ? 'pass' : 'fail',
|
|
393
|
+
findings,
|
|
394
|
+
}
|
|
395
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/* Copyright (c) 2025 Richard Rodger, MIT License */
|
|
2
|
+
|
|
3
|
+
// SARIF rendering for a vet report (G2 phase 5,
|
|
4
|
+
// docs/capability-review/g2-validation-verb.md).
|
|
5
|
+
//
|
|
6
|
+
// A MINIMAL SARIF 2.1.0 profile, and deliberately nothing more: one
|
|
7
|
+
// run, one `result` per finding, the finding's first site as the
|
|
8
|
+
// primary location, its remaining sites under `relatedLocations`, and
|
|
9
|
+
// the whole finding object embedded in `properties` so a SARIF consumer
|
|
10
|
+
// still holds the native contract. No fixes, no code flows, no
|
|
11
|
+
// baselines — the JSON report is the native contract, SARIF is the
|
|
12
|
+
// interchange skin CI systems already ingest (GitHub code scanning
|
|
13
|
+
// uploads, PR annotation).
|
|
14
|
+
//
|
|
15
|
+
// This is library API, not CLI plumbing, for the same reason the vet
|
|
16
|
+
// engine is: an embedder (and G7's MCP server later) must be able to
|
|
17
|
+
// emit the interchange form without shelling out. The Go twin is
|
|
18
|
+
// go/report_sarif.go, and the two are held to byte parity over the
|
|
19
|
+
// shared fixture pair in test/spec/files/vet-sarif/ — with the message
|
|
20
|
+
// text and producer version redacted, exactly as test/spec/vet.tsv
|
|
21
|
+
// carves the message out of its goldens, because prose and the two
|
|
22
|
+
// independent version series are deliberately not in cross-port parity.
|
|
23
|
+
|
|
24
|
+
import type { VetReport, VetFinding, VetSite } from './vet'
|
|
25
|
+
|
|
26
|
+
import { exactJSON } from './exactjson'
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
// SARIF levels are error/warning/note; the report's severities are
|
|
30
|
+
// error/warning/info. Only `info` needs translating, but the map spells
|
|
31
|
+
// out all three so a new severity fails loudly here rather than
|
|
32
|
+
// silently emitting itself.
|
|
33
|
+
const SARIF_LEVEL: Record<string, string> = {
|
|
34
|
+
error: 'error',
|
|
35
|
+
warning: 'warning',
|
|
36
|
+
info: 'note',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
// A site's file is a filesystem path, and a SARIF artifactLocation.uri
|
|
41
|
+
// is a URI reference: `#`, `%`, spaces and every other URI-significant
|
|
42
|
+
// character must be percent-encoded or a consumer parses the path as
|
|
43
|
+
// something else (text after `#` becomes a fragment). Encoded BY BYTE
|
|
44
|
+
// over UTF-8, with RFC 3986's unreserved and path characters kept
|
|
45
|
+
// literal, so the Go twin produces identical bytes from an identical
|
|
46
|
+
// loop (go/report_sarif.go sarifURI).
|
|
47
|
+
function sarifUri(path: string): string {
|
|
48
|
+
const bytes = Buffer.from(path, 'utf8')
|
|
49
|
+
let out = ''
|
|
50
|
+
for (const b of bytes) {
|
|
51
|
+
const c = String.fromCharCode(b)
|
|
52
|
+
if (b < 128 && /[A-Za-z0-9\-._~/!$&'()*+,;=:@]/.test(c)) {
|
|
53
|
+
out += c
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
out += '%' + b.toString(16).toUpperCase().padStart(2, '0')
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return out
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
function sarifLocation(site: VetSite): any {
|
|
64
|
+
const physical: any = {
|
|
65
|
+
artifactLocation: { uri: sarifUri(site.file) },
|
|
66
|
+
}
|
|
67
|
+
// SARIF regions are 1-based. A finding with no position — a parse
|
|
68
|
+
// failure reports at -1:-1 — gets a location with no region rather
|
|
69
|
+
// than an invented one.
|
|
70
|
+
if (1 <= site.row) {
|
|
71
|
+
physical.region = { startColumn: site.col, startLine: site.row }
|
|
72
|
+
}
|
|
73
|
+
return { physicalLocation: physical }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
function sarifResult(finding: VetFinding): any {
|
|
78
|
+
// The engine orders sites data-first (the thing to fix), so the first
|
|
79
|
+
// site is the primary location and the rest are related — which for a
|
|
80
|
+
// two-site conflict puts the schema's declaration under
|
|
81
|
+
// `relatedLocations`, exactly where a code-scanning UI shows "the
|
|
82
|
+
// other side".
|
|
83
|
+
const result: any = {
|
|
84
|
+
level: SARIF_LEVEL[finding.severity],
|
|
85
|
+
locations: [sarifLocation(finding.sites[0])],
|
|
86
|
+
message: { text: finding.message },
|
|
87
|
+
properties: finding,
|
|
88
|
+
ruleId: 'aontu/' + finding.code,
|
|
89
|
+
}
|
|
90
|
+
const related = finding.sites.slice(1)
|
|
91
|
+
if (0 < related.length) {
|
|
92
|
+
result.relatedLocations = related.map(sarifLocation)
|
|
93
|
+
}
|
|
94
|
+
return result
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Render a vet report as SARIF 2.1.0 text (a minimal profile: one run,
|
|
100
|
+
* one result per finding, the finding embedded in `properties`).
|
|
101
|
+
*
|
|
102
|
+
* @param report A report from `vet()`.
|
|
103
|
+
* @param version The producer version for `tool.driver.version` —
|
|
104
|
+
* the CLI passes its package version; the two ports'
|
|
105
|
+
* version series are independent by design.
|
|
106
|
+
* @returns The SARIF JSON text, indented two spaces, keys in
|
|
107
|
+
* the canonical emitter's sorted order.
|
|
108
|
+
*/
|
|
109
|
+
function sarifReport(report: VetReport, version: string): string {
|
|
110
|
+
return exactJSON({
|
|
111
|
+
$schema: 'https://json.schemastore.org/sarif-2.1.0.json',
|
|
112
|
+
runs: [{
|
|
113
|
+
// An `error` verdict means the run could not be set up (an
|
|
114
|
+
// unusable schema): zero findings from a FAILED run must not
|
|
115
|
+
// read like zero findings from a clean one, so the failure is
|
|
116
|
+
// carried in SARIF's own invocation metadata rather than by an
|
|
117
|
+
// indistinguishable empty result list.
|
|
118
|
+
invocations: [{
|
|
119
|
+
executionSuccessful: 'error' !== report.verdict,
|
|
120
|
+
}],
|
|
121
|
+
results: report.findings.map(sarifResult),
|
|
122
|
+
tool: {
|
|
123
|
+
driver: {
|
|
124
|
+
informationUri: 'https://github.com/rjrodger/aontu',
|
|
125
|
+
name: 'aontu',
|
|
126
|
+
version,
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
}],
|
|
130
|
+
version: '2.1.0',
|
|
131
|
+
}, 2)
|
|
132
|
+
} /* node:coverage ignore next 6 */
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
export {
|
|
136
|
+
sarifReport,
|
|
137
|
+
}
|
package/src/site.ts
CHANGED
|
@@ -6,12 +6,45 @@ import type {
|
|
|
6
6
|
} from './type'
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
type SiteSpec = {
|
|
9
|
+
type SiteSpec = {
|
|
10
|
+
row?: number, col?: number, url?: string, len?: number, src?: string,
|
|
11
|
+
}
|
|
10
12
|
|
|
13
|
+
// Site locates a value in the text that produced it: where it starts
|
|
14
|
+
// (row, col), HOW FAR IT RUNS (len), and the source text itself (src).
|
|
15
|
+
//
|
|
16
|
+
// THE EXTENT IS NOT OPTIONAL DETAIL — it is what makes a site safe to
|
|
17
|
+
// edit. Without it the only length available is the CANON, and canon is
|
|
18
|
+
// not source text: `port: 0x1F` has canon `31`, so replacing
|
|
19
|
+
// `(col, canon.length)` writes `port: 5x1F` and corrupts the document.
|
|
20
|
+
// The parser knew the extent all along — the token carries `len` and
|
|
21
|
+
// `src` beside the `rI`/`cI` this already read — and dropping it was
|
|
22
|
+
// the whole defect (status report 2026-08-21, §5).
|
|
23
|
+
//
|
|
24
|
+
// WHAT THE EXTENT COVERS is the TOKEN THE SITE POINTS AT, which is the
|
|
25
|
+
// same thing row and col have always pointed at:
|
|
26
|
+
//
|
|
27
|
+
// 0x1F a scalar literal len 4 the whole literal
|
|
28
|
+
// "hi there" a string len 10 quotes included
|
|
29
|
+
// {x:1} a map len 1 the opening brace
|
|
30
|
+
// min(3) a constraint len 3 the name, not the call
|
|
31
|
+
//
|
|
32
|
+
// So for a SCALAR — which is what a repair edits, and what `set` and
|
|
33
|
+
// the manual fallback rewrite — `(col, len)` is exactly the span to
|
|
34
|
+
// replace. For a container it under-reaches to the opening delimiter,
|
|
35
|
+
// which is safe in the direction that matters: a highlight that is too
|
|
36
|
+
// short is a cosmetic flaw, while canon.length on a map over-reaches
|
|
37
|
+
// across lines into text the value never occupied.
|
|
38
|
+
//
|
|
39
|
+
// -1 and '' mean UNKNOWN, the same convention row and col already use:
|
|
40
|
+
// a value minted by unification rather than written by a document has
|
|
41
|
+
// no site, and must not be edited as though it had one.
|
|
11
42
|
class Site {
|
|
12
43
|
row: number
|
|
13
44
|
col: number
|
|
14
45
|
url: string
|
|
46
|
+
len: number
|
|
47
|
+
src: string
|
|
15
48
|
|
|
16
49
|
constructor(val?: Val | SiteSpec) {
|
|
17
50
|
const site = ((val as any)?.site ?? val) as SiteSpec
|
|
@@ -19,6 +52,8 @@ class Site {
|
|
|
19
52
|
this.row = site?.row ?? -1
|
|
20
53
|
this.col = site?.col ?? -1
|
|
21
54
|
this.url = site?.url ?? ''
|
|
55
|
+
this.len = site?.len ?? -1
|
|
56
|
+
this.src = site?.src ?? ''
|
|
22
57
|
}
|
|
23
58
|
} /* node:coverage ignore next 6 */
|
|
24
59
|
|
package/src/std.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/* Copyright (c) 2025 Richard Rodger, MIT License */
|
|
2
|
+
|
|
3
|
+
// THE BUNDLED VOCABULARY (G4 phase 4,
|
|
4
|
+
// docs/capability-review/g4-identity-relations.md): `@"std/system"` is
|
|
5
|
+
// served from the engine itself — no filesystem, no package resolution
|
|
6
|
+
// — so a document may use it under every include capability except
|
|
7
|
+
// `none`, and the hermeticity posture is not widened by a source that
|
|
8
|
+
// never leaves the process.
|
|
9
|
+
//
|
|
10
|
+
// The TEXT is the shared artifact: go/std.go carries the same bytes,
|
|
11
|
+
// and test/spec/std-system.tsv pins its canon and its canon-hash in
|
|
12
|
+
// both engines, so the two copies cannot drift without a red suite.
|
|
13
|
+
// It carries no backtick for that reason: one string literal per port,
|
|
14
|
+
// and Go's raw string has no escape.
|
|
15
|
+
|
|
16
|
+
const STD_SYSTEM = `# std/system --- the SYSTEM VOCABULARY (G4 phase 4). Ports, components
|
|
17
|
+
# and relations need no syntax: they are schemas. Everything here is
|
|
18
|
+
# ordinary unification --- conjunction, spreads, marks, defaults ---
|
|
19
|
+
# so the vocabulary costs the language nothing, and an author who wants
|
|
20
|
+
# a different one writes it the same way.
|
|
21
|
+
#
|
|
22
|
+
# EXPERIMENTAL until the distribution layer can version it by
|
|
23
|
+
# canon-hash. Entity ids deliberately do NOT embed versions: fusing
|
|
24
|
+
# identity and version makes "v1 and v2 describe the same entity"
|
|
25
|
+
# inexpressible.
|
|
26
|
+
|
|
27
|
+
std: {
|
|
28
|
+
|
|
29
|
+
# One end of a connection.
|
|
30
|
+
Port: type({
|
|
31
|
+
direction: *in | out | inout
|
|
32
|
+
protocol?: string
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
# A node with ports. A Component that is not itself an entity is a
|
|
36
|
+
# component OF its nearest identified ancestor, which is the
|
|
37
|
+
# entity/component distinction and needs no mark of its own.
|
|
38
|
+
Component: type({
|
|
39
|
+
ports?: {&: $.std.Port}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
# A component that is a service. Written out rather than as
|
|
43
|
+
# $.std.Component & {kind: service}: a reference from one member of
|
|
44
|
+
# this file to another does not survive being INCLUDED into a
|
|
45
|
+
# document (the marks the include carries make the referring member
|
|
46
|
+
# unusable), so the vocabulary states each schema on its own.
|
|
47
|
+
Service: type({
|
|
48
|
+
kind: service
|
|
49
|
+
ports?: {&: $.std.Port}
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
# A DECLARED RELATION. "target" is what the far end must satisfy,
|
|
53
|
+
# "inverse" names the relation that must mirror it, and "acyclic"
|
|
54
|
+
# asks that the edge set have no cycle. The last two are checked
|
|
55
|
+
# AFTER unification, not by it: they are global and non-monotone ---
|
|
56
|
+
# an acyclic graph becomes cyclic when one more edge unifies in ---
|
|
57
|
+
# and no lattice citizen may be falsified by more information.
|
|
58
|
+
Relation: type({
|
|
59
|
+
target?: top
|
|
60
|
+
inverse?: string
|
|
61
|
+
acyclic?: *false | boolean
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
`
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
// The bundled sources, by the name a document writes. Both the bare
|
|
68
|
+
// name and the `.aon` spelling resolve, because both are what an
|
|
69
|
+
// author reaches for.
|
|
70
|
+
export const STD_SOURCES: Record<string, string> = {
|
|
71
|
+
'std/system': STD_SYSTEM,
|
|
72
|
+
'std/system.aon': STD_SYSTEM,
|
|
73
|
+
}
|