@githonllc/avouch 0.1.10 → 0.1.11
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/SPEC.md +3 -3
- package/dist/src/checker.js +14 -4
- package/dist/src/query.d.ts +1 -0
- package/dist/src/query.js +1 -0
- package/docs/adapter-guide.md +1 -1
- package/ontology.schema.json +7 -2
- package/package.json +1 -1
- package/vocab.ttl +5 -0
package/SPEC.md
CHANGED
|
@@ -67,7 +67,7 @@ The definitions quoted in the "Palantir analog" column are short quotations from
|
|
|
67
67
|
| `permission` … `principals` | extension | none | optional on a keys-form, an `any_of` alternative or the `{none}` form: `{kinds: [<actor kind>, …], cite}`; an actor of another kind is denied; the cite is checked by R2, not by R10; R1, R2 |
|
|
68
68
|
| `scope` | extension | none | optional tenant scope `{by, global?: [{type, cite}], root?: {type, cite}, cite}`; unlisted object types are tenant types; `cite` supports `by` only; R1, R2, R6 |
|
|
69
69
|
| `dispositions` | extension | none | optional result vocabulary `{<ID>: {class: rejected \| recorded \| pending \| applied, cite}}`; decision rows name these ids; R1, R2 |
|
|
70
|
-
| `stateMachines` | extension | none | R3 |
|
|
70
|
+
| `stateMachines` | extension | none | R1, R3; `stateMachines.<O>.property` (optional) names the property of `<O>` that holds its state; absent, it is `config.stateProperty` |
|
|
71
71
|
| `idempotencyKey` | extension | none | `{required, cite}`: whether the caller must supply an idempotency key. Absent = unspecified (not `false`). A missing key on a `required: true` action is rejected at request shape, before `decide` (so before its shape step), permission and the envelope; it is not an invocation result of `decide`, gives no receipt and is not reconciled. Never derived from effects. R1, R2 |
|
|
72
72
|
| `evidence` | extension | none | id of the executed evidence, resolved by the evidence adapter; R1, R6, R7 |
|
|
73
73
|
| `cite` / `knownSourceGaps` | extension | none | provenance and waivers; R2 |
|
|
@@ -115,9 +115,9 @@ The adapter may read the ontology instance (for example to list action ids in a
|
|
|
115
115
|
|
|
116
116
|
| Rule | Checks | Facts used | Guarantee | Not guaranteed |
|
|
117
117
|
|---|---|---|---|---|
|
|
118
|
-
| R1 | shape, references resolve (a `{param}` read names a parameter of its action; `actor` is `id`, `keys` or `kind`), ids unique, `formatVersion`, evidence ids exist; expressions type-check (section 9; the properties, parameters and derived properties they read declare `type`; `self` only in derived expressions; quantifier variables do not reuse reserved or bound names; set literals are non-empty with one element type), derived properties have exactly one of `expr` / `reads` and form no cycle, a derived `expr` has `type` and `cite`; a condition has exactly one of `expr` / `unspecified`, `reads` only with `unspecified`; duplicate condition ids are R1 (implementation decision: each entry with an `expr` is type-checked, but every entry with that id takes its type error, R4 reads and literals, and the R12 derived-property part from the last entry with that id that has an `expr`; the static UNKNOWN analysis also uses that last entry, while `decide` evaluates the first entry with that id); `scope` types are declared object types, each listed once; disposition ids are not invocation results; decision rows name a condition of the action and a disposition, only `pending` results have `next` (and must), `next.actions` are declared actions; `principals` is `{kinds: [distinct], cite}`; `idempotencyKey` is `{required: boolean, cite}`; a parameter with `values` has `type: enum` and a non-empty list of distinct non-empty strings, and a string literal compared with that parameter (`eq` / `neq` with `{ref: <name>}` on the other side, or every element of the set literal in `in [{ref: <name>}, {lit: [..]}]`) must be one of its `values` (else a type error); a parameter with `projectedFrom` has `type: {ref: <Object>}` and a non-empty list of distinct non-empty field names, and then each field names a declared parameter of the same action that is not projected (a request input); with `config.idempotencyDeclarationRequired: true` (default `false`) an action without `idempotencyKey` fails `idempotency_undeclared` (key `<ID>`) | `config.actionIdPattern`, `config.stateProperty`, `evidence` keys, `config.idempotencyDeclarationRequired` | structure is well-formed | that an expression means what its cite says |
|
|
118
|
+
| R1 | shape, references resolve (a `{param}` read names a parameter of its action; `actor` is `id`, `keys` or `kind`), ids unique, `formatVersion`, evidence ids exist; expressions type-check (section 9; the properties, parameters and derived properties they read declare `type`; `self` only in derived expressions; quantifier variables do not reuse reserved or bound names; set literals are non-empty with one element type), derived properties have exactly one of `expr` / `reads` and form no cycle, a derived `expr` has `type` and `cite`; a condition has exactly one of `expr` / `unspecified`, `reads` only with `unspecified`; duplicate condition ids are R1 (implementation decision: each entry with an `expr` is type-checked, but every entry with that id takes its type error, R4 reads and literals, and the R12 derived-property part from the last entry with that id that has an `expr`; the static UNKNOWN analysis also uses that last entry, while `decide` evaluates the first entry with that id); `scope` types are declared object types, each listed once; disposition ids are not invocation results; decision rows name a condition of the action and a disposition, only `pending` results have `next` (and must), `next.actions` are declared actions; `principals` is `{kinds: [distinct], cite}`; `idempotencyKey` is `{required: boolean, cite}`; a parameter with `values` has `type: enum` and a non-empty list of distinct non-empty strings, and a string literal compared with that parameter (`eq` / `neq` with `{ref: <name>}` on the other side, or every element of the set literal in `in [{ref: <name>}, {lit: [..]}]`) must be one of its `values` (else a type error); a parameter with `projectedFrom` has `type: {ref: <Object>}` and a non-empty list of distinct non-empty field names, and then each field names a declared parameter of the same action that is not projected (a request input); `stateMachines.<O>.property`, when present, is a non-empty string that names a declared property of `<O>` (key `stateMachines.<O>.property`); `canonical_values` on the state property of an object with a machine (`stateMachines.<O>.property`, default `config.stateProperty`) are states of that machine; with `config.idempotencyDeclarationRequired: true` (default `false`) an action without `idempotencyKey` fails `idempotency_undeclared` (key `<ID>`) | `config.actionIdPattern`, `config.stateProperty`, `evidence` keys, `config.idempotencyDeclarationRequired` | structure is well-formed | that an expression means what its cite says |
|
|
119
119
|
| R2 | anchors unique, quotes verbatim (also under `derivedProperties`, `scope` and `dispositions`), properties are fields, names in the store catalog, parameter names appear in their quote (`parameter_not_in_quote`), or, for a projected parameter, each of its fields (`projection_field_not_in_quote`, key `<ID>:<name>:<field>`), and so does each of a parameter's `values` (`parameter_value_not_in_quote`, key `<ID>:<name>:<value>`); with `scope`: each tenant type's field list has `<by>` (`scope_field_missing`, key `<Object>.<by>`), no global type's field list has it (`scope_field_present`), `<by>` is in the scope quote (`scope_by_not_in_quote`, key `scope`); each disposition id is in its own quote (`disposition_not_in_quote`, key `dispositions.<ID>`); a declared `type` has a cite (`type_without_provenance`, key `<Object>.<prop>` or `derivedProperties.<id>`; a derived property with `expr` and no cite is R1 only) | `section`, `containsTerm`, `fieldList`, `storeCatalog` | quotes exist in the source | that a quote supports the claim |
|
|
120
|
-
| R3 | graph equality, terminal states, reachability, `by` = triggers, `by` iff the action edits the state property | `stateMachines`, `transitionTriggers`, `config.stateProperty` | ontology matches the source machine | trigger-cell conditions; objects without a machine in the source |
|
|
120
|
+
| R3 | graph equality, terminal states, reachability, `by` = triggers, `by` iff the action edits the state property of the object (`stateMachines.<O>.property`, default `config.stateProperty`) | `stateMachines`, `transitionTriggers`, `config.stateProperty` | ontology matches the source machine | trigger-cell conditions; objects without a machine in the source |
|
|
121
121
|
| R4 | conditions read only canonical and policy properties, for an expression also through the derived properties it reaches (parameter and actor reads are request inputs, not state, and are not classified; a derived property without `expr` is not expanded; a hand-written `reads` entry that names a derived property is expanded through its `reads`, or, when it has an `expr`, through the properties that expression reads, as for an expression condition); a non-canonical property passes when every read compares it by `eq` / `neq` with a literal in its `canonical_values`; canonical values and the values of a parameter read appear in their quote (`parameter_value_unsupported`); each literal of an expression appears in one of its condition's quotes (an empty string never appears), or in the derived property's quote (`literal_unsupported`, key `<ID>:<condition id>:<value>` or `derivedProperties.<id>:<value>`) | none | the property reads of conditions are canonical or policy facts; literals are not invented | that `reads` is complete; that an expression's logic matches its cite |
|
|
122
122
|
| R5 | object membership, cross-context declaration (`via` or `unspecified`), `create_rule`, entity coverage | `contextMembers`, `config.crossContextMechanisms` | object ownership matches the source | the content of the mechanism |
|
|
123
123
|
| R6 | link sweep, link catalog complete (with `scope`, the `<by>` field of a tenant type counts as declared and must not be in `non_link_fields`: `non_link_field_stray`), `none_but_written` | `fieldList`, `config.linkFieldPattern`, `evidence` | link effects are declared completely | that an effect text is right |
|
package/dist/src/checker.js
CHANGED
|
@@ -100,6 +100,8 @@ export function check(ontology, facts) {
|
|
|
100
100
|
for (const n of Object.keys(stateMachines))
|
|
101
101
|
if (!objType(n))
|
|
102
102
|
v("R1", `stateMachines.${n}`, "not a declared object type");
|
|
103
|
+
// the state property of an object: its machine's `property` when that is a non-empty string, else the profile default
|
|
104
|
+
const stateProp = (o) => (own(stateMachines, o) && isObj(stateMachines[o]) && isStr(stateMachines[o].property) ? stateMachines[o].property : config.stateProperty);
|
|
103
105
|
const gaps = arr(ont.knownSourceGaps);
|
|
104
106
|
if (!Array.isArray(ont.knownSourceGaps))
|
|
105
107
|
v("R1", "knownSourceGaps", "missing (use [])");
|
|
@@ -135,7 +137,7 @@ export function check(ontology, facts) {
|
|
|
135
137
|
v("R1", `${p}.properties.${pn}.canonical_values`, "needs values[] and cite");
|
|
136
138
|
else if (pr.class === "canonical")
|
|
137
139
|
v("R1", `${p}.properties.${pn}.canonical_values`, "only meaningful on a non-canonical property");
|
|
138
|
-
else if (pn ===
|
|
140
|
+
else if (pn === stateProp(name) && states)
|
|
139
141
|
for (const x of cv.values)
|
|
140
142
|
if (!states.has(x))
|
|
141
143
|
v("R1", `${p}.properties.${pn}.canonical_values`, `${x} is not a ${name} state`);
|
|
@@ -152,6 +154,14 @@ export function check(ontology, facts) {
|
|
|
152
154
|
const cls = objects[o].properties[p].class;
|
|
153
155
|
return CLASSES.includes(cls) ? cls : null; // a property without a valid class does not resolve (R1 reports it)
|
|
154
156
|
};
|
|
157
|
+
for (const [n, sm] of Object.entries(stateMachines)) {
|
|
158
|
+
if (!objType(n) || !isObj(sm) || sm.property === undefined)
|
|
159
|
+
continue; // an undeclared object or a machine that is not a mapping is R1 above / below
|
|
160
|
+
if (!isStr(sm.property))
|
|
161
|
+
v("R1", `stateMachines.${n}.property`, "must be a non-empty string");
|
|
162
|
+
else if (propClass(`${n}.${sm.property}`) === null)
|
|
163
|
+
v("R1", `stateMachines.${n}.property`, `${sm.property} does not resolve to a declared property of ${n}`);
|
|
164
|
+
}
|
|
155
165
|
const linkIds = new Set();
|
|
156
166
|
for (const [i, l] of links.entries()) {
|
|
157
167
|
const p = `linkTypes[${i}]`;
|
|
@@ -554,13 +564,13 @@ export function check(ontology, facts) {
|
|
|
554
564
|
for (const [cid, c] of Object.entries(commands)) {
|
|
555
565
|
if (!isObj(c))
|
|
556
566
|
continue;
|
|
557
|
-
const stateEdits = new Set(arr(c.edits).filter((e) => e.endsWith(`.${
|
|
567
|
+
const stateEdits = new Set(arr(c.edits).filter((e) => e.endsWith(`.${stateProp(e.split(".")[0])}`)).map((e) => e.split(".")[0]));
|
|
558
568
|
for (const o of stateEdits)
|
|
559
569
|
if (objType(o) && isObj(stateMachines[o]) && !byCmds.get(o)?.has(cid))
|
|
560
|
-
v("R3", `${cid}:${o}.${
|
|
570
|
+
v("R3", `${cid}:${o}.${stateProp(o)}`, `${cid} edits ${o}.${stateProp(o)} but is in no ${o} transition's by`);
|
|
561
571
|
for (const [o, cs] of byCmds)
|
|
562
572
|
if (cs.has(cid) && !stateEdits.has(o))
|
|
563
|
-
v("R3", `${cid}:${o}.${
|
|
573
|
+
v("R3", `${cid}:${o}.${stateProp(o)}`, `${cid} is in a ${o} transition's by but does not edit ${o}.${stateProp(o)}`);
|
|
564
574
|
}
|
|
565
575
|
// ------------------------------------------------------------------------------------------ R4
|
|
566
576
|
for (const [cid, c] of Object.entries(commands)) {
|
package/dist/src/query.d.ts
CHANGED
package/dist/src/query.js
CHANGED
package/docs/adapter-guide.md
CHANGED
|
@@ -61,7 +61,7 @@ Parameters and parameter and actor reads (`parameters`, `{param}`, `{actor}`) ne
|
|
|
61
61
|
- `crossContextMechanisms`: the legal `via` values of `crossContext` (R5).
|
|
62
62
|
- `actionIdPattern`: the pattern of an action id (R1).
|
|
63
63
|
- `linkFieldPattern`: the pattern of a field that holds a link (R6).
|
|
64
|
-
- `stateProperty`: the name of the state property (R1, R3).
|
|
64
|
+
- `stateProperty`: the default name of the state property (R1, R3). An ontology machine names another property of its object with `stateMachines.<Object>.property`.
|
|
65
65
|
|
|
66
66
|
## 6. Run the example
|
|
67
67
|
|
package/ontology.schema.json
CHANGED
|
@@ -315,7 +315,7 @@
|
|
|
315
315
|
"values",
|
|
316
316
|
"cite"
|
|
317
317
|
],
|
|
318
|
-
"description": "Values of a NON-canonical property that the source explicitly marks as canonical. Extension (R1, R4); no Palantir analog. R1: needs values[] (non-empty) and cite; not allowed when class is canonical; on the
|
|
318
|
+
"description": "Values of a NON-canonical property that the source explicitly marks as canonical. Extension (R1, R4); no Palantir analog. R1: needs values[] (non-empty) and cite; not allowed when class is canonical; on the state property of an object with `stateMachines` (`stateMachines.<Object>.property`, default the configured state property), each value must be a state of that object. R4: each value must occur as a whole identifier in this cite's quote (else R4 `canonical_value_unsupported`); a condition value read `{prop, values}` of this property passes R4 only if all its values are in this list.",
|
|
319
319
|
"properties": {
|
|
320
320
|
"values": {
|
|
321
321
|
"type": "array",
|
|
@@ -409,12 +409,17 @@
|
|
|
409
409
|
"terminal",
|
|
410
410
|
"transitions"
|
|
411
411
|
],
|
|
412
|
-
"description": "State machine of the object. Extension (R1, R3); no Palantir analog. R3 compares it with the machine the adapter extracts from the source: the state set, the initial state and the transition set must be equal; `terminal` must equal the source states that have no outgoing edge; every source state must be reachable from the initial state. Also R3: an action appears in some `by` of this object's transitions if and only if its `edits` contains `<Object>.<state property>`.",
|
|
412
|
+
"description": "State machine of the object. Extension (R1, R3); no Palantir analog. R3 compares it with the machine the adapter extracts from the source: the state set, the initial state and the transition set must be equal; `terminal` must equal the source states that have no outgoing edge; every source state must be reachable from the initial state. Also R3: an action appears in some `by` of this object's transitions if and only if its `edits` contains `<Object>.<state property>`. The state property is `property` when it is given, else the configured state property (profile `stateProperty`, default `state`).",
|
|
413
413
|
"properties": {
|
|
414
414
|
"doc": {
|
|
415
415
|
"$ref": "#/$defs/anchor",
|
|
416
416
|
"description": "The anchor of the source section that holds the state machine (R3: must be exactly the anchor the adapter reports for this object)."
|
|
417
417
|
},
|
|
418
|
+
"property": {
|
|
419
|
+
"type": "string",
|
|
420
|
+
"minLength": 1,
|
|
421
|
+
"description": "Optional. The property of the object that holds its state. Absent: the configured state property (profile `stateProperty`, default `state`). Extension (R1, R3); no Palantir analog. R1: must be a non-empty string and a declared property of the object (else R1, key `stateMachines.<Name>.property`). R1 and R3 use it in place of the configured state property for this object only: the `canonical_values` state check, and `by` if and only if `edits` contains `<Object>.<property>`."
|
|
422
|
+
},
|
|
418
423
|
"initial": {
|
|
419
424
|
"type": "string",
|
|
420
425
|
"minLength": 1,
|
package/package.json
CHANGED
package/vocab.ttl
CHANGED
|
@@ -137,6 +137,11 @@ ofv:stateMachines a rdf:Property ;
|
|
|
137
137
|
rdfs:comment "State machine of an object type. YAML: stateMachines.<name>" ;
|
|
138
138
|
rdfs:isDefinedBy <https://w3id.org/avouch/v1/vocab> .
|
|
139
139
|
|
|
140
|
+
ofv:property a rdf:Property ;
|
|
141
|
+
rdfs:label "property" ;
|
|
142
|
+
rdfs:comment "Property of the object that holds its state; default the configured state property. YAML: stateMachines.<name>.property" ;
|
|
143
|
+
rdfs:isDefinedBy <https://w3id.org/avouch/v1/vocab> .
|
|
144
|
+
|
|
140
145
|
ofv:initial a rdf:Property ;
|
|
141
146
|
rdfs:label "initial" ;
|
|
142
147
|
rdfs:comment "Initial state. YAML: stateMachines.<name>.initial" ;
|