eyeling 1.13.6 → 1.14.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/HANDBOOK.md CHANGED
@@ -1516,6 +1516,41 @@ A tiny `sprintf` subset:
1516
1516
  - Any other specifier (`%d`, `%f`, …) causes the builtin to fail.
1517
1517
  - Missing arguments are treated as empty strings.
1518
1518
 
1519
+ ### Length and character utilities (Eyeling extensions)
1520
+
1521
+ Eyeling also implements a few **non-standard** `string:` helpers that are handy for string-based algorithms. These are **not** part of the SWAP builtin set, so treat them as Eyeling extensions.
1522
+
1523
+ #### `string:length`
1524
+
1525
+ **Shape:** `s string:length n`
1526
+
1527
+ Casts `s` to a string and returns its length as an integer literal token.
1528
+
1529
+ #### `string:charAt`
1530
+
1531
+ **Shape:** `( s i ) string:charAt ch`
1532
+
1533
+ - `i` is a numeric term, truncated to an integer.
1534
+ - Indexing is **0-based** (like JavaScript).
1535
+ - If `i` is out of range, `ch` is the empty string `""`.
1536
+
1537
+ #### `string:setCharAt`
1538
+
1539
+ **Shape:** `( s i ch ) string:setCharAt out`
1540
+
1541
+ Returns a copy of `s` with the character at index `i` (0-based) replaced by:
1542
+
1543
+ - the **first character** of `ch` if `ch` is non-empty, otherwise
1544
+ - the empty string.
1545
+
1546
+ If `i` is out of range, `out` is the original string.
1547
+
1548
+ #### `string:hammingDistance`
1549
+
1550
+ **Shape:** `( a b ) string:hammingDistance d`
1551
+
1552
+ Returns the number of differing positions between `a` and `b`. Fails if the two strings have different lengths.
1553
+
1519
1554
  ### Containment and prefix/suffix tests
1520
1555
 
1521
1556
  - `string:contains`
@@ -1941,6 +1976,24 @@ References:
1941
1976
 
1942
1977
  If you are running untrusted inputs, consider `--super-restricted` to disable all builtins except implication.
1943
1978
 
1979
+ ### Eyeling fast-path builtins
1980
+
1981
+ Eyeling ships one optional fast-path builtins builtin e.g. used by the genetic-algorithm example:
1982
+
1983
+ #### `urn:eyeling:ga:solveString`
1984
+
1985
+ **Shape:** `( target mutationProbability samples seed traceEvery maxGenerations ) urn:eyeling:ga:solveString ( generation score value seed )`
1986
+
1987
+ - `target` and `value` are `xsd:string` literals.
1988
+ - `mutationProbability` is interpreted as a percentage per character (0–100).
1989
+ - `traceEvery` controls debug tracing:
1990
+ - `0` disables tracing,
1991
+ - `1` traces every generation,
1992
+ - `N` traces every `N` generations.
1993
+ - `maxGenerations` is a safety cap:
1994
+ - `0` means unlimited,
1995
+ - otherwise the builtin stops once `generation >= maxGenerations` (even if not solved).
1996
+
1944
1997
  ### A.6 Skolemization and `log:skolem`
1945
1998
 
1946
1999
  When forward rule heads contain blank nodes (existentials), Eyeling replaces them with generated Skolem IRIs so derived facts are ground.
package/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  A compact [Notation3 (N3)](https://notation3.org/) reasoner in **JavaScript**.
4
4
 
5
- - Single self-contained bundle (`eyeling.js`), no external runtime deps
6
- - Forward (`=>`) + backward (`<=`) chaining over Horn-style rules
7
- - **CLI / npm `reason()` output is mode-dependent by default**: **newly derived forward facts** in normal mode, or when top-level `{ ... } log:query { ... }.` directives are present the **unique instantiated conclusion triples** of those queries (a forward-rule-like projection), optionally with compact proof comments
5
+ - Single self-contained bundle (`eyeling.js`), no external runtime dependencies
6
+ - Forward (`=>`) and backward (`<=`) chaining over Horn-style rules
7
+ - **CLI / npm `reason()` output is mode-dependent by default**: it prints **newly derived forward facts** in normal mode, or (when top-level `{ ... } log:query { ... }.` directives are present) the **unique instantiated conclusion triples** of those queries, optionally with compact proof comments
8
8
  - Works in Node.js and fully client-side (browser/worker)
9
9
 
10
10
  ## Links
@@ -15,9 +15,7 @@ A compact [Notation3 (N3)](https://notation3.org/) reasoner in **JavaScript**.
15
15
  - **Notation3 test suite:** [https://codeberg.org/phochste/notation3tests](https://codeberg.org/phochste/notation3tests)
16
16
  - **Eyeling conformance report:** [https://codeberg.org/phochste/notation3tests/src/branch/main/reports/report.md](https://codeberg.org/phochste/notation3tests/src/branch/main/reports/report.md)
17
17
 
18
- Eyeling is regularly checked against the community Notation3 test suite; the report above tracks current pass/fail results.
19
-
20
- If you want to understand how the parser, unifier, proof search, skolemization, scoped closure, and builtins are implemented, start with the handbook.
18
+ Eyeling is regularly checked against the community Notation3 test suite. If you want implementation details (parser, unifier, proof search, skolemization, scoped closure, builtins), start with the handbook.
21
19
 
22
20
  ## Quick start
23
21
 
@@ -31,7 +29,7 @@ If you want to understand how the parser, unifier, proof search, skolemization,
31
29
  npm i eyeling
32
30
  ```
33
31
 
34
- ### CLI
32
+ ## CLI usage
35
33
 
36
34
  Run on a file:
37
35
 
@@ -39,23 +37,33 @@ Run on a file:
39
37
  npx eyeling examples/socrates.n3
40
38
  ```
41
39
 
42
- See all options:
40
+ Show all options:
43
41
 
44
42
  ```bash
45
43
  npx eyeling --help
46
44
  ```
47
45
 
48
- ### log:query output selection
46
+ Useful flags include `--proof-comments`, `--stream`, `--strings`, and `--enforce-https`.
47
+
48
+ ## What gets printed?
49
+
50
+ ### Normal mode (default)
51
+
52
+ Without top-level `log:query` directives, Eyeling prints **newly derived forward facts** by default.
53
+
54
+ ### `log:query` mode (output selection)
49
55
 
50
- If your input contains one or more **top-level** directives of the form:
56
+ If the input contains one or more **top-level** directives of the form:
51
57
 
52
58
  ```n3
53
59
  { ?x a :Human. } log:query { ?x a :Mortal. }.
54
60
  ```
55
61
 
56
- Eyeling will still compute the saturated forward closure, but it will **print only** the **unique instantiated conclusion triples** of those `log:query` directives (instead of printing all newly derived forward facts).
62
+ Eyeling still computes the saturated forward closure, but it **prints only** the **unique instantiated conclusion triples** of those `log:query` directives (instead of all newly derived forward facts).
57
63
 
58
- ### JavaScript API
64
+ ## JavaScript API
65
+
66
+ ### npm helper: `reason()`
59
67
 
60
68
  CommonJS:
61
69
 
@@ -69,7 +77,7 @@ const input = `
69
77
  :Socrates a :Human.
70
78
  :Human rdfs:subClassOf :Mortal.
71
79
 
72
- { ?S a ?A. ?A rdfs:subClassOf ?B } => { ?S a ?B }.
80
+ { ?s a ?A. ?A rdfs:subClassOf ?B. } => { ?s a ?B. }.
73
81
  `;
74
82
 
75
83
  console.log(reason({ proofComments: false }, input));
@@ -79,37 +87,57 @@ ESM:
79
87
 
80
88
  ```js
81
89
  import eyeling from 'eyeling';
90
+
82
91
  console.log(eyeling.reason({ proofComments: false }, input));
83
92
  ```
84
93
 
85
- Streaming / in-process reasoning (browser/worker, direct `eyeling.js`):
94
+ Notes:
95
+
96
+ - `reason()` returns the same textual output you would get from the CLI for the same input/options.
97
+ - By default, the npm helper keeps output machine-friendly (`proofComments: false`).
98
+ - The npm helper shells out to the bundled `eyeling.js` CLI for simplicity and robustness.
99
+
100
+ ### Direct bundle / browser-worker API: `reasonStream()`
101
+
102
+ For in-process reasoning (browser, worker, or direct use of `eyeling.js`):
86
103
 
87
104
  ```js
88
- const { closureN3 } = eyeling.reasonStream(input, {
105
+ const result = eyeling.reasonStream(input, {
89
106
  proof: false,
90
107
  onDerived: ({ triple }) => console.log(triple),
108
+ // includeInputFactsInClosure: false,
91
109
  });
92
110
 
93
- // `closureN3` is also mode-dependent:
94
- // - normal forward mode: closure (input facts + derived facts) by default
95
- // - `log:query` mode: the query-selected triples
96
- // To exclude input facts from the normal-mode closure, pass:
97
- // includeInputFactsInClosure: false
98
- // The return value also includes `queryMode`, `queryTriples`, and `queryDerived`.
111
+ console.log(result.closureN3);
99
112
  ```
100
113
 
101
- > Note: the npm `reason()` helper shells out to the bundled `eyeling.js` CLI for simplicity and robustness.
114
+ #### `reasonStream()` output behavior
115
+
116
+ `closureN3` is also mode-dependent:
117
+
118
+ - **Normal mode:** by default, `closureN3` is the closure (**input facts + derived facts**)
119
+ - **`log:query` mode:** `closureN3` is the **query-selected triples**
120
+
121
+ To exclude input facts from the normal-mode closure, pass:
122
+
123
+ ```js
124
+ includeInputFactsInClosure: false;
125
+ ```
126
+
127
+ The returned object also includes `queryMode`, `queryTriples`, and `queryDerived` (and in normal mode, `onDerived` fires for newly derived facts; in `log:query` mode it fires for the query-selected derived triples).
102
128
 
103
129
  ## Builtins
104
130
 
105
- Builtins are defined in [eyeling-builtins.ttl](https://github.com/eyereasoner/eyeling/blob/main/eyeling-builtins.ttl) and described in the [HANDBOOK](https://eyereasoner.github.io/eyeling/HANDBOOK#ch11).
131
+ Builtins are defined in [eyeling-builtins.ttl](https://github.com/eyereasoner/eyeling/blob/main/eyeling-builtins.ttl) and described in the [Handbook (Chapter 11)](https://eyereasoner.github.io/eyeling/HANDBOOK#ch11).
106
132
 
107
- ## Testing (repo checkout)
133
+ ## Development and testing (repo checkout)
108
134
 
109
135
  ```bash
110
136
  npm test
111
137
  ```
112
138
 
139
+ You can also inspect the `examples/` directory for many small and large N3 programs.
140
+
113
141
  ## License
114
142
 
115
- MIT (see [LICENSE](https://github.com/eyereasoner/eyeling/blob/main/LICENSE.md)).
143
+ MIT see [LICENSE.md](https://github.com/eyereasoner/eyeling/blob/main/LICENSE.md).
@@ -0,0 +1,63 @@
1
+ # ==========================================================================================
2
+ # Genetic algorithm demo
3
+ #
4
+ # Genetic algorithm (GA) in a nutshell:
5
+ # - Start with a random candidate string of the same length as the target.
6
+ # - Repeatedly create a batch of “children” by copying the current best string and randomly
7
+ # mutating each character with a small probability.
8
+ # - Score each child by how many positions differ from the target (Hamming distance).
9
+ # - Keep the best-scoring child as the new current string and iterate until the score is 0.
10
+ # This is a simple hill-climbing GA (selection + mutation, no crossover), using a seeded RNG
11
+ # so runs are reproducible.
12
+ #
13
+ # Uses Eyeling's builtin:
14
+ # urn:eyeling:ga:solveString
15
+ #
16
+ # Configure:
17
+ # :mutationProbability 5 # percent per character
18
+ # :samples 80
19
+ # :seed 100
20
+ # :traceEvery 0 # 0=off, 1=every generation, 100=every 100 generations
21
+ # :maxGenerations 0 # 0=unlimited
22
+ # ==========================================================================================
23
+
24
+ @prefix : <http://example.org/ga-solve#>.
25
+ @prefix ega: <urn:eyeling:ga:>.
26
+ @prefix log: <http://www.w3.org/2000/10/swap/log#>.
27
+ @prefix string: <http://www.w3.org/2000/10/swap/string#>.
28
+
29
+ :cfg
30
+ :mutationProbability 5;
31
+ :samples 80;
32
+ :seed 100;
33
+ :traceEvery 1;
34
+ :maxGenerations 0.
35
+
36
+ # solveResult(TargetString) = (generation score value seed)
37
+ { ?TargetString :solveResult ( ?Gen ?Score ?Value ?Seed ) } <= {
38
+ :cfg :mutationProbability ?MutProb;
39
+ :samples ?Samples;
40
+ :seed ?Seed;
41
+ :traceEvery ?TraceEvery;
42
+ :maxGenerations ?MaxG.
43
+
44
+ ( ?TargetString ?MutProb ?Samples ?Seed ?TraceEvery ?MaxG )
45
+ ega:solveString
46
+ ( ?Gen ?Score ?Value ?SeedOut ) .
47
+
48
+ # By design, the 4th output is the (input) seed.
49
+ ?SeedOut log:equalTo ?Seed
50
+ }.
51
+
52
+ # Boolean-ish API for querying success
53
+ { ?TargetString :solved true } <= {
54
+ ?TargetString :solveResult ( ?Gen 0 ?TargetString ?Seed )
55
+ }.
56
+
57
+ # --------------------------------------------------------
58
+ # Requested output: solve('METHINKS IT IS LIKE A WEASEL').
59
+ # --------------------------------------------------------
60
+
61
+ { "METHINKS IT IS LIKE A WEASEL" :solved true .
62
+ ( "solve('%s').\n" "METHINKS IT IS LIKE A WEASEL" ) string:format ?Line
63
+ } => { 1 log:outputString ?Line }.
@@ -0,0 +1,195 @@
1
+ # ===================================================================================
2
+ # odrl-dpv-campaign-audit.n3
3
+ #
4
+ # A "reasoning about reasoning" example using both ODRL and DPV.
5
+ #
6
+ # What this demonstrates
7
+ # ----------------------
8
+ # 1) Object-level reasoning inside a quoted policy theory:
9
+ # - ODRL permissions specify who may read which dataset
10
+ # - DPV annotations specify policy purpose and data category
11
+ # - local rules translate ODRL + DPV metadata into effective access summaries
12
+ #
13
+ # 2) Meta-level reasoning outside that quoted theory:
14
+ # - compute the closure of the quoted policy theory
15
+ # - inspect what the policy theory entails
16
+ # - derive an audit decision based on those entailed consequences
17
+ #
18
+ # Why this is "reasoning about reasoning"
19
+ # ---------------------------------------
20
+ # The final audit decision is derived from statements ABOUT the closure of the quoted
21
+ # policy theory (what the policy entails), not directly from the raw asserted policy.
22
+ #
23
+ # Modeling note
24
+ # -------------
25
+ # This is a pedagogical example:
26
+ # - ODRL is used for policy structure (permission/assignee/target/action)
27
+ # - DPV is used for privacy semantics (purpose, personal data categories)
28
+ # - local rules map the combination into audit-friendly facts
29
+ # ===================================================================================
30
+
31
+ @prefix : <http://example.org/#>.
32
+ @prefix ey: <https://eyereasoner.github.io/ns#>.
33
+ @prefix odrl: <http://www.w3.org/ns/odrl/2/>.
34
+ @prefix dpv: <http://www.w3.org/ns/dpv#>.
35
+ @prefix log: <http://www.w3.org/2000/10/swap/log#>.
36
+
37
+ # -----------------------------------------------
38
+ # OBJECT THEORY (QUOTED ODRL + DPV POLICY THEORY)
39
+ # -----------------------------------------------
40
+
41
+ :case :policyTheory {
42
+ # ---- Request context (inside the quoted theory)
43
+ :ticket-99 :request :PublishCampaignAccessSummary.
44
+
45
+ # ---- Recipients (local classification for audit decisions)
46
+ :adVendor a :ExternalRecipient.
47
+ :marketingTeam a :InternalRecipient.
48
+
49
+ # ---- Datasets (annotated with DPV data category)
50
+ :AudienceList dpv:hasPersonalData dpv:PersonalData.
51
+ :ProductCatalog a :NonPersonalDataset.
52
+
53
+ # ---- ODRL policy A (benign-ish): internal team reads product catalog for service provision
54
+ :policy-internal a odrl:Set;
55
+ dpv:hasPurpose dpv:ServiceProvision;
56
+ odrl:permission [
57
+ odrl:assignee :marketingTeam;
58
+ odrl:target :ProductCatalog;
59
+ odrl:action odrl:read
60
+ ].
61
+
62
+ # ---- ODRL policy B (risky for publication): external vendor reads audience list for marketing
63
+ :policy-external a odrl:Set;
64
+ dpv:hasPurpose dpv:Marketing;
65
+ odrl:permission [
66
+ odrl:assignee :adVendor;
67
+ odrl:target :AudienceList;
68
+ odrl:action odrl:read
69
+ ].
70
+
71
+ # --------------------------------------------------------------------
72
+ # Object-level translation / normalization rules (pedagogical mapping)
73
+ # --------------------------------------------------------------------
74
+
75
+ # ODRL read permission -> effective read access
76
+ {
77
+ ?Policy odrl:permission [
78
+ odrl:assignee ?Recipient;
79
+ odrl:target ?Dataset;
80
+ odrl:action odrl:read
81
+ ].
82
+ }
83
+ =>
84
+ {
85
+ ?Recipient :canRead ?Dataset.
86
+ }.
87
+
88
+ # Lift policy purpose to the recipient (summary view used by the audit layer)
89
+ {
90
+ ?Policy dpv:hasPurpose ?Purpose.
91
+ ?Policy odrl:permission [
92
+ odrl:assignee ?Recipient;
93
+ odrl:target ?Dataset;
94
+ odrl:action odrl:read
95
+ ].
96
+ }
97
+ =>
98
+ {
99
+ ?Recipient :authorisedPurpose ?Purpose.
100
+ }.
101
+
102
+ # Combine ODRL permission target with DPV data-category annotation on the dataset
103
+ {
104
+ ?Policy odrl:permission [
105
+ odrl:assignee ?Recipient;
106
+ odrl:target ?Dataset;
107
+ odrl:action odrl:read
108
+ ].
109
+ ?Dataset dpv:hasPersonalData ?Category.
110
+ }
111
+ =>
112
+ {
113
+ ?Recipient :authorisedReadCategory ?Category.
114
+ }.
115
+ }.
116
+
117
+ # --------------------------
118
+ # META-LEVEL POLICY ANALYSIS
119
+ # --------------------------
120
+
121
+ # 1) Compute the closure of the quoted ODRL+DPV policy theory.
122
+ {
123
+ :case :policyTheory ?PolicyTheory.
124
+ ?PolicyTheory log:conclusion ?PolicyClosure.
125
+ }
126
+ =>
127
+ {
128
+ :case :policyClosure ?PolicyClosure.
129
+ }.
130
+
131
+ # 2) Record a witness if the policy closure entails:
132
+ # external recipient + marketing purpose + personal-data access.
133
+ #
134
+ # This is a statement ABOUT what the quoted theory entails.
135
+ {
136
+ :case :policyClosure ?C.
137
+ ?C log:includes {
138
+ ?Recipient a :ExternalRecipient.
139
+ ?Recipient :authorisedPurpose dpv:Marketing.
140
+ ?Recipient :authorisedReadCategory dpv:PersonalData.
141
+ }.
142
+ }
143
+ =>
144
+ {
145
+ :case :entails {
146
+ ?Recipient a :ExternalRecipient.
147
+ ?Recipient :authorisedPurpose dpv:Marketing.
148
+ ?Recipient :authorisedReadCategory dpv:PersonalData.
149
+ }.
150
+ :case :risk :ThirdPartyMarketingPersonalDataAccessEntailed.
151
+ }.
152
+
153
+ # 3) If the request is to publish the campaign access summary and the closure entails the
154
+ # risky pattern above, require manual review.
155
+ {
156
+ :case :policyTheory ?PolicyTheory.
157
+ ?PolicyTheory log:includes {
158
+ :ticket-99 :request :PublishCampaignAccessSummary.
159
+ }.
160
+ :case :risk :ThirdPartyMarketingPersonalDataAccessEntailed.
161
+ }
162
+ =>
163
+ {
164
+ :case :decision :ManualReviewRequired.
165
+ }.
166
+
167
+ # 4) Otherwise, if the same request is present but the closure does NOT entail any external
168
+ # recipient with marketing-purpose personal-data access, auto-approve.
169
+ #
170
+ # Important: log:notIncludes applies to FORMULAS, so it is applied to ?C (the closure).
171
+ {
172
+ :case :policyTheory ?PolicyTheory.
173
+ ?PolicyTheory log:includes {
174
+ :ticket-99 :request :PublishCampaignAccessSummary.
175
+ }.
176
+ :case :policyClosure ?C.
177
+ ?C log:notIncludes {
178
+ [] a :ExternalRecipient.
179
+ [] :authorisedPurpose dpv:Marketing.
180
+ [] :authorisedReadCategory dpv:PersonalData.
181
+ }.
182
+ }
183
+ =>
184
+ {
185
+ :case :decision :AutoApprove.
186
+ }.
187
+
188
+ # ------------
189
+ # QUERY INTENT
190
+ # ------------
191
+
192
+ # These top-level log:query directives define the query-projected outputs.
193
+ { :case :decision ?Decision. } log:query { :case :decision ?Decision. }.
194
+ { :case :entails ?Witness. } log:query { :case :entails ?Witness. }.
195
+