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 +53 -0
- package/README.md +53 -25
- package/examples/genetic-algorithm.n3 +63 -0
- package/examples/odrl-dpv-campaign-audit.n3 +195 -0
- package/examples/odrl-dpv-conflict-audit.n3 +264 -0
- package/examples/odrl-policy-audit.n3 +147 -0
- package/examples/output/genetic-algorithm.n3 +3 -0
- package/examples/output/odrl-dpv-campaign-audit.n3 +10 -0
- package/examples/output/odrl-dpv-conflict-audit.n3 +12 -0
- package/examples/output/odrl-policy-audit.n3 +6 -0
- package/eyeling-builtins.ttl +17 -0
- package/eyeling.js +208 -0
- package/lib/builtins.js +208 -0
- package/package.json +1 -1
- package/test/examples.test.js +18 -68
- package/test/n3gen.test.js +23 -87
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
|
|
6
|
-
- Forward (`=>`)
|
|
7
|
-
- **CLI / npm `reason()` output is mode-dependent by default**: **newly derived forward facts** in normal mode, or
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
40
|
+
Show all options:
|
|
43
41
|
|
|
44
42
|
```bash
|
|
45
43
|
npx eyeling --help
|
|
46
44
|
```
|
|
47
45
|
|
|
48
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
{ ?
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 [
|
|
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
|
-
##
|
|
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
|
|
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
|
+
|