@githonllc/avouch 0.1.8 → 0.1.10
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 +35 -12
- package/dist/src/checker.js +19 -4
- package/dist/src/contract.d.ts +1 -1
- package/dist/src/contract.js +2 -2
- package/dist/src/evaluate.js +9 -3
- package/dist/src/expr.d.ts +5 -0
- package/dist/src/expr.js +9 -1
- package/dist/src/query.d.ts +1 -0
- package/dist/src/query.js +1 -0
- package/ontology.schema.json +13 -8
- package/package.json +1 -1
- package/vocab.ttl +7 -2
package/SPEC.md
CHANGED
|
@@ -28,6 +28,17 @@ Only generic needs go into the format. A special-case need stays in the project
|
|
|
28
28
|
|
|
29
29
|
Example: objects shared by several contexts (a shared kernel) are a cross-project pattern. The format already expresses them: the shared objects get their own context, and actions that write them declare `crossContext`. An exemption such as "writes to shared objects need no declaration" fails criteria 2 and 3, so it is not in the format.
|
|
30
30
|
|
|
31
|
+
### Limit of the expression language
|
|
32
|
+
|
|
33
|
+
The expression language (section 9) is a restricted predicate language that can be checked statically. It reads domain facts and decides the conditions of decision tables. It is not a language in which a project implements its rules, for two reasons: the ontology states the rules independently of the runtime, so that agreement in `reconcile` means something; and the static checks of section 9 (types, the UNKNOWN analysis) must stay decidable.
|
|
34
|
+
|
|
35
|
+
So a new expression node must meet the three criteria above and do one of these two things:
|
|
36
|
+
|
|
37
|
+
- read a fact that no node reads now: of the snapshot, the invocation or the caller, including a field of a parameter or of a stored value;
|
|
38
|
+
- decide a bounded claim over facts: a comparison, a quantifier over a finite collection, or a count or sum over a finite collection inside a comparison.
|
|
39
|
+
|
|
40
|
+
Value nodes such as `{lit}`, `plus` and `dateIn` only give a comparison its operands. Not admitted: user-defined functions, recursion, unbounded iteration, nodes that build new collections, records or strings, and other arithmetic than `plus`, `dateIn` and the count or sum above.
|
|
41
|
+
|
|
31
42
|
## 2. Concepts
|
|
32
43
|
|
|
33
44
|
A key is foundation when Palantir Foundry has the same concept (names follow https://www.palantir.com/docs/foundry/ontology/overview/ and the pages for each type). A key is extension when Palantir has no analog; the rule that uses it is named.
|
|
@@ -46,8 +57,8 @@ The definitions quoted in the "Palantir analog" column are short quotations from
|
|
|
46
57
|
| `derivedProperties.<d>.expr` / `cite` / `type` | foundation | derived property definition | the definition as an expression over `self` (instead of `reads`), its source text and its declared type; a predicate expression has type `boolean`; without `expr` the derived property is undefined for evaluation; R1, R2, R4, R12 |
|
|
47
58
|
| `linkTypes` | foundation | link type: "the schema definition of a relationship between two object types" | `via` / `table` map to a foreign key or a many-to-many datasource; `create_rule` is an extension (R5) |
|
|
48
59
|
| `actionTypes` | foundation | action type: "the schema definition of a set of changes or edits to objects, property values, and links that a user can take at once" | |
|
|
49
|
-
| `actionTypes.<A>.parameters` | foundation | action parameter: "Parameters are the inputs of an action type." | name → `{cite, type?, optional?, values?}`; read by a condition as `{param}` or, in an expression, `{ref}`; `type` is needed when an expression reads it; `optional: true` lets the caller omit it; `values` (only with `type: enum`) is the value domain of the parameter; R1, R2 |
|
|
50
|
-
| `conditions` | foundation + constraint | submission criteria: "the conditions that determine whether an action can be submitted"; Palantir submission criteria ≈ the rows whose disposition class is `rejected` | named Boolean claims `{id, expr \| unspecified, reads?, reads_unspecified?, cites[{cite}], scenarios?}`: `expr` is the claim as an expression (section 9); `unspecified` says it is not written as one, and `reads` may then list what it reads (properties, parameters `{param, values?}`, the caller `{actor: id}` / `{actor: keys}`); R1, R4, R12 |
|
|
60
|
+
| `actionTypes.<A>.parameters` | foundation | action parameter: "Parameters are the inputs of an action type." | name → `{cite, type?, optional?, values?, projectedFrom?}`; read by a condition as `{param}` or, in an expression, `{ref}`; `type` is needed when an expression reads it; `optional: true` lets the caller omit it; `values` (only with `type: enum`) is the value domain of the parameter; `projectedFrom` lists the request fields, each a declared parameter of the same action, that the project resolves to this ref; it takes effect only on a `type: {ref: <Object>}` parameter with at least one field, and `decide` requires the binding of such a projected parameter, also when optional: an absent binding is `INVALID_BINDING` `missing` (section 9); on any other parameter R1 reports it and `decide` ignores it; R1, R2 |
|
|
61
|
+
| `conditions` | foundation + constraint | submission criteria: "the conditions that determine whether an action can be submitted"; Palantir submission criteria ≈ the rows whose disposition class is `rejected` | named Boolean claims `{id, expr \| unspecified, reads?, reads_unspecified?, cites[{cite}], scenarios?}`: `expr` is the claim as an expression (section 9); `unspecified` says it is not written as one, and `reads` may then list what it reads (properties, parameters `{param, values?}`, the caller `{actor: id}` / `{actor: keys}` / `{actor: kind}`); R1, R4, R12 |
|
|
51
62
|
| `decision` | extension | none | decision table `{hitPolicy: first, order?: {cite}, rows: [{when: {passes \| fails: <condition>}, result, cites, next?}], otherwise?: {result, cites, next?}}`; `next` is `{actions}`, `{external, cite}` or `{unspecified}`; R1, R12 |
|
|
52
63
|
| `edits` / `creates` / `deletes` | foundation | action rules: modify / create / delete object | `deletes` is a hard delete of datasource rows; a soft delete is an `edits` |
|
|
53
64
|
| `link_effects` | foundation + constraint | action rules: create / delete link | every incident link needs an entry, including `none` / `unspecified` (R6 sweep) |
|
|
@@ -104,10 +115,10 @@ The adapter may read the ontology instance (for example to list action ids in a
|
|
|
104
115
|
|
|
105
116
|
| Rule | Checks | Facts used | Guarantee | Not guaranteed |
|
|
106
117
|
|---|---|---|---|---|
|
|
107
|
-
| R1 | shape, references resolve (a `{param}` read names a parameter of its action; `actor` is `id` or `
|
|
108
|
-
| 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`), 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 |
|
|
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 |
|
|
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 |
|
|
109
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 |
|
|
110
|
-
| 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, 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 |
|
|
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 |
|
|
111
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 |
|
|
112
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 |
|
|
113
124
|
| R7 | observed writes are declared; events are in `emits`; changed columns are declared (`column_undeclared`, key `<ID>:<Object>.<prop>`); declared effects are witnessed (`effect_unwitnessed`); with `config.evidenceRequired: true` (default `false`) an action without `evidence` fails `evidence_missing` (key `<ID>`) | `evidence`, `config.evidenceRequired` | every store changed by a committed invocation is in the touched objects' `datasource`, a pure link table with an `effect`, or `infrastructureStores`; every observed event is in `emits`; every column changed on an existing row of a touched object's `datasource` is, for each touched object of that store, a property of a resolvable `edits` entry of that same object (objects may share a store; a column that one object declares is still reported for each other touched object of the store that does not declare it); every resolvable `edits` entry (resolved as R1 resolves it; an unresolvable entry is R1 only) was changed on an existing row (key `<ID>:<Object>.<prop>`), every `creates` object had a row inserted (key `<ID>:creates:<Object>`), every `deletes` object had a row deleted (key `<ID>:deletes:<Object>`) and every pure link table with an `effect` had a row inserted or deleted (key `<ID>:link:<link id>`) by at least one committed invocation | actions without evidence (unless `evidenceRequired`); that a not yet witnessed effect is unreachable; atomicity, which refers to the committed effect (one invocation may have several attempts) and which the project profile or evidence adapter must enforce |
|
|
@@ -146,7 +157,7 @@ The table lists only the optional facts. Every rule may also read the required f
|
|
|
146
157
|
|
|
147
158
|
## 8. Versions
|
|
148
159
|
|
|
149
|
-
The version is in two places: `v1` in the schema `$id`, and `formatVersion: 1` in the ontology file (R1 fails on any other value). A change that breaks existing ontologies raises both. A change that rejects only files without meaning (for example an empty name) is not a break. Adding the `permission.any_of` form is additive: existing files stay valid, so `formatVersion` stays 1. Adding `parameters` and the parameter and actor reads of the submission criteria (since renamed `conditions`) is additive: existing files stay valid, so `formatVersion` stays 1. Adding `values` on enum parameters is additive; `formatVersion` stays 1. Adding `deletes` is additive; `formatVersion` stays 1.
|
|
160
|
+
The version is in two places: `v1` in the schema `$id`, and `formatVersion: 1` in the ontology file (R1 fails on any other value). A change that breaks existing ontologies raises both. A change that rejects only files without meaning (for example an empty name) is not a break. Adding the `permission.any_of` form is additive: existing files stay valid, so `formatVersion` stays 1. Adding `parameters` and the parameter and actor reads of the submission criteria (since renamed `conditions`) is additive: existing files stay valid, so `formatVersion` stays 1. Adding `values` on enum parameters is additive; `formatVersion` stays 1. Adding `deletes` is additive; `formatVersion` stays 1. Adding `projectedFrom` on ref parameters is additive; `formatVersion` stays 1. Adding the ref `actor.kind` (and the read `{actor: kind}`) is additive; `formatVersion` stays 1. Requiring each `projectedFrom` field to be a declared parameter rejects only files that fail at run time (the field reaches `decide` as an undeclared binding, `INVALID_BINDING` `unknown_param`); `formatVersion` stays 1. For an optional projected parameter whose field is missing or null, the projection now passes null (it omitted the binding before); `decide` is unchanged, and `formatVersion` stays 1.
|
|
150
161
|
During 0.x the v1 draft changes in place (`formatVersion` 1, `$id` v1); the rule above applies from the 1.0 semantic freeze.
|
|
151
162
|
|
|
152
163
|
## 9. Decision semantics
|
|
@@ -155,7 +166,7 @@ Stage 1A; runtime consistency is Stage 1B. Code: `src/expr.ts` (AST, types, stat
|
|
|
155
166
|
|
|
156
167
|
### Expressions and two-level evaluation
|
|
157
168
|
|
|
158
|
-
Every node is a single-key map. The reserved names `now`, `actor` and `self` are exported as `RESERVED_NAMES`. The node set is fixed (`EXPR_NODES`):
|
|
169
|
+
Every node is a single-key map. The reserved names `now`, `actor` and `self` are exported as `RESERVED_NAMES`. The node set is fixed (`EXPR_NODES`); section 1, "Limit of the expression language", says which new nodes can be admitted:
|
|
159
170
|
|
|
160
171
|
| Level | Nodes | Result |
|
|
161
172
|
|---|---|---|
|
|
@@ -165,7 +176,7 @@ Every node is a single-key map. The reserved names `now`, `actor` and `self` are
|
|
|
165
176
|
|
|
166
177
|
`{passes: c}` and `{fails: c}` are not expression nodes; they occur only in the `when` of a decision row. An omitted `where` is T.
|
|
167
178
|
|
|
168
|
-
Names in `{ref}` (at most one dot): `now` (timestamp), `actor.id` (id), `actor.keys` (`{set: string}`), a parameter, a quantifier variable, and `self` (only in a derived property expression). `x.prop` reads a declared, typed property of the object binding `x`; a derived property is read only through `{derived}`; `x.id` is not supported (write `{ref: x}`). A quantifier variable must not reuse a reserved name, a parameter name or an outer variable. A derived expression sees only `self`, `now`, `actor` and its own quantifier variables.
|
|
179
|
+
Names in `{ref}` (at most one dot): `now` (timestamp), `actor.id` (id), `actor.keys` (`{set: string}`), `actor.kind` (`enum`: the kind of the caller, as in `principals`; no value domain is declared, so the value of a literal compared with it is checked only by R4 `literal_unsupported`, and, as for any two `enum` values, a comparison with another `enum` property or parameter type-checks whether or not it has a meaning), a parameter, a quantifier variable, and `self` (only in a derived property expression). `x.prop` reads a declared, typed property of the object binding `x`; a derived property is read only through `{derived}`; `x.id` is not supported (write `{ref: x}`). A quantifier variable must not reuse a reserved name, a parameter name or an outer variable. A derived expression sees only `self`, `now`, `actor` and its own quantifier variables.
|
|
169
180
|
|
|
170
181
|
### Types and values
|
|
171
182
|
|
|
@@ -219,8 +230,8 @@ Soundness contract: for each combination of null / non-null over the nullable re
|
|
|
219
230
|
Nullability comes from the facts and the parameters; only this analysis treats unknown nullability as null:
|
|
220
231
|
|
|
221
232
|
- `fieldList` gives each field with `nullable` (a trailing `?` in the source). A field is non-null only when its object's field list says `nullable: false`. Unknown nullability is analysed as may-be-null: a field that is not listed, or an object without a field list (R2 reports both).
|
|
222
|
-
- `now` and `actor.id` / `actor.keys` are never null. A quantifier variable or `self` is not null; its property `x.prop` may be null unless the field is non-null.
|
|
223
|
-
- An optional parameter may be null, and so may every property read through it. A required parameter is not null (a missing one is `INVALID_BINDING`); its property may be null unless the field is non-null.
|
|
233
|
+
- `now` and `actor.id` / `actor.keys` / `actor.kind` are never null. A quantifier variable or `self` is not null; its property `x.prop` may be null unless the field is non-null.
|
|
234
|
+
- An optional parameter may be null, and so may every property read through it. A required parameter is not null (a missing one, or a null projected one, is `INVALID_BINDING`); its property may be null unless the field is non-null.
|
|
224
235
|
|
|
225
236
|
Counting: the analysis collects the distinct normalized paths that may be null and that the outer evaluation reads (`self` is replaced by the `of` of the derived property that is inlined). Reads only inside a quantifier are not counted, because a quantifier is T or F outside; a read that also occurs outside a quantifier is counted there. The `from` of a navigation is never counted: a null ref navigates to the empty set, not to U. With k paths, all 2^k combinations are evaluated; for k > 8 (`MAX_NULLABLE_REFS`) the condition is taken as may-be-U without enumeration. A quantifier body is analysed on its own with the same evaluator and the same counting, its variable added to the visible names; a quantifier nested in the body is again T or F there and is analysed on its own.
|
|
226
237
|
|
|
@@ -264,14 +275,24 @@ A request-shape check before `decide` (so before its shape step), permission and
|
|
|
264
275
|
|
|
265
276
|
| Step | Result |
|
|
266
277
|
|---|---|
|
|
267
|
-
| shape (no snapshot read): undeclared parameter (first by sorted name), missing required
|
|
278
|
+
| shape (no snapshot read): undeclared parameter (first by sorted name), missing parameter (in declaration order: a required one that is absent or null, except a null projected parameter; a projected parameter that is absent, also when optional), value not fitting its type, or an `enum` value not among the parameter's `values` | `INVALID_BINDING` with `unknown_param`, `missing`, `type` |
|
|
268
279
|
| permission: each alternative is denied (a key missing, or `principals: {kinds, cite}` whose `kinds` lacks `actor.kind`; the cite is not read), passed (all keys and conditional keys), or undecided (only conditional keys missing); the `{none}` form passes, or is denied when its `principals` `kinds` lacks `actor.kind` (never undecided) | `PERMISSION_DENIED`; INDETERMINATE `permission_condition` / `permission_unknown` |
|
|
269
280
|
| envelope (idempotency) | `REPLAYED`, `IDEMPOTENCY_CONFLICT`; **not implemented by `decide`**, which has no idempotency key |
|
|
270
|
-
| binding and scope: parameters are checked in declaration order, across all parameters (scalar ref and `{set: {ref: T}}`), and the first failing parameter gives the result; each ref parameter is looked up in its own type only; each element of a `{set: {ref: T}}` parameter is bound the same way, in array order, and the first failing element gives the result | `SCOPE_DENIED` (the id exists only outside the scope), `INVALID_BINDING` `not_found` (absent from the type, including ids of other types) |
|
|
281
|
+
| binding and scope: parameters are checked in declaration order, across all parameters (scalar ref and `{set: {ref: T}}`), and the first failing parameter gives the result; each ref parameter is looked up in its own type only; each element of a `{set: {ref: T}}` parameter is bound the same way, in array order, and the first failing element gives the result; a required projected parameter with the value null fails as `not_found` | `SCOPE_DENIED` (the id exists only outside the scope), `INVALID_BINDING` `not_found` (absent from the type, including ids of other types) |
|
|
271
282
|
| decision: first-hit table | a domain result `{result, class, row}` or INDETERMINATE |
|
|
272
283
|
|
|
273
284
|
The invocation results are a fixed set (`INVOCATION_RESULTS`), not declared by actions. A null optional ref binds; reading its properties gives null. Invalid input (unknown action, scope argument against the declaration, an ill-typed condition, an untyped parameter, a parameter named `now`, `actor` or `self`, a decision row naming an unknown condition, a decision row or `otherwise` whose `result` has no disposition, a type both global and root, a corrupt or incomplete snapshot) throws; it is never a result. A row without a field that evaluation reads (such as the scope field) is incomplete; a field with the value null is not.
|
|
274
285
|
|
|
286
|
+
Projected parameters. A `{ref: T}` parameter with `projectedFrom: [<field>, …]` (at least one field) is projected: it is not a field of the request. Each of its fields is a declared parameter of the same action that is not itself projected (R1): a request input, given and checked as any other parameter. The projection only adds the projected binding; it does not remove the fields from the bindings. The project's projection reads those request fields (an alternate key, for example `source` and `external_ref`) and looks up the object of the parameter's ref type as the runtime looks it up: with the same scope rules (only in the caller's scope for a tenant or root type; everywhere for a global type) and the same soft delete filter. It never passes the id of an object outside the scope. When any of the fields is missing from the request or is null, the projection does not look up: for an optional parameter it passes null, and for a required parameter it omits the binding. Else, when no object has the key, it passes null; else it passes the id. Null is not the same as an absent binding:
|
|
287
|
+
|
|
288
|
+
| Binding | Required parameter | Optional parameter |
|
|
289
|
+
|---|---|---|
|
|
290
|
+
| absent | `INVALID_BINDING` `missing` (shape): a field is missing from the request or is null | `INVALID_BINDING` `missing` (shape): the projection did not give the binding, an error of the project |
|
|
291
|
+
| null | `INVALID_BINDING` `not_found` (binding and scope), as for an id that does not resolve | an ordinary value: a field is missing or null, or no object has the key; for example "no other object has this key", which a condition tests with `isNull` |
|
|
292
|
+
| id | bound as any ref parameter | bound as any ref parameter |
|
|
293
|
+
|
|
294
|
+
For a required parameter, an absent binding is the expected input when a field is missing (`decide` gives `missing`); when the runtime gives another result, `reconcile` shows the difference as a mismatch. For an optional parameter, an absent binding is never a valid input: `decide` gives `missing`, so a projection that did not run shows as a mismatch in `reconcile` and never passes as "no object has this key". When a field is a required parameter and is missing or null, the result is `INVALID_BINDING` `missing` whatever the projection passes; the rule for an optional parameter matters only when its fields are optional. On any other parameter (another type, for example `{set: {ref: T}}`, or no field) R1 reports `projectedFrom` and `decide` ignores it: the parameter behaves as without `projectedFrom`. The format does not read the fields or their values; it has no alternate-key model.
|
|
295
|
+
|
|
275
296
|
### Decision table and INDETERMINATE
|
|
276
297
|
|
|
277
298
|
`passes(c) := eval(c) = T`; `fails(c) := eval(c) != T`. Rows are tried in order; the first hit gives `{layer: domain, result, class, row, next?}`, with `class` from `dispositions`. Only the rows actually reached matter. INDETERMINATE is neither an invocation result nor a domain result, and is never filled in as rejected or applied. Its reasons:
|
|
@@ -341,3 +362,5 @@ The command line reads and writes these types as JSON lines, one `RuntimeCase` o
|
|
|
341
362
|
### Known limit
|
|
342
363
|
|
|
343
364
|
`dateIn` uses the time zone data of the runtime. Near a time zone rule change the evaluator and another runtime with a different tzdata version may give different dates.
|
|
365
|
+
|
|
366
|
+
An optional projected parameter carries a fact that the project computes from the same database as the runtime, for example the object that already holds a unique key. Avouch checks the decision given that fact, not whether the fact is right. When the projection and the runtime are wrong in the same way (for example the projection passes null and the runtime has lost its unique index), they agree and `reconcile` gives `match`.
|
package/dist/src/checker.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Format-level ontology checker (rules R1–R12). Pure functions: no I/O. Project facts arrive through SourceFacts (contract.ts).
|
|
2
2
|
// Fail closed: a missing anchor, a parse failure or a missing field is a violation, never a skip.
|
|
3
3
|
import { IDENT, RULES } from "./contract.js";
|
|
4
|
-
import { derivedPropertyType, isObj, newReads } from "./expr.js";
|
|
4
|
+
import { derivedPropertyType, isObj, newReads, projectedFields } from "./expr.js";
|
|
5
5
|
import { analyzeConditions, asReads, parseScope, quotesOf, stage1Violations } from "./stage1-check.js";
|
|
6
6
|
export { IDENT };
|
|
7
7
|
const CLASSES = ["canonical", "derived", "policy", "unknown"];
|
|
@@ -284,6 +284,14 @@ export function check(ontology, facts) {
|
|
|
284
284
|
const vs = isObj(pv) ? pv.values : undefined;
|
|
285
285
|
if (vs !== undefined && !(pv.type === "enum" && Array.isArray(vs) && vs.length > 0 && vs.every(isStr) && new Set(vs).size === vs.length))
|
|
286
286
|
v("R1", `${p}.parameters.${n}`, "values needs type enum and a non-empty list of distinct non-empty strings");
|
|
287
|
+
const pf = isObj(pv) ? pv.projectedFrom : undefined;
|
|
288
|
+
if (pf !== undefined && !(isObj(pv.type) && isStr(pv.type.ref) && Array.isArray(pf) && pf.length > 0 && pf.every(isStr) && new Set(pf).size === pf.length))
|
|
289
|
+
v("R1", `${p}.parameters.${n}`, "projectedFrom needs type {ref: <Object>} and a non-empty list of distinct non-empty field names");
|
|
290
|
+
// Each field is a request input: the projection reads it from the request and only adds its own binding.
|
|
291
|
+
else if (pf !== undefined)
|
|
292
|
+
for (const f of pf)
|
|
293
|
+
if (!own(params, f) || projectedFields(isObj(params[f]) ? params[f] : {}) !== null)
|
|
294
|
+
v("R1", `${p}.parameters.${n}`, `projectedFrom field ${f} must be a declared parameter of ${cid} that is not projected`);
|
|
287
295
|
}
|
|
288
296
|
const pids = new Set();
|
|
289
297
|
for (const [i, pc] of arr(c.conditions).entries()) {
|
|
@@ -306,8 +314,8 @@ export function check(ontology, facts) {
|
|
|
306
314
|
v("R1", `${p}.conditions.${pc.id}`, `parameter read ${JSON.stringify(r)}: values must be a non-empty list of non-empty strings`);
|
|
307
315
|
}
|
|
308
316
|
else if (isObj(r) && "actor" in r) {
|
|
309
|
-
if (r.actor !== "id" && r.actor !== "keys")
|
|
310
|
-
v("R1", `${p}.conditions.${pc.id}`, `actor read ${JSON.stringify(r)} must be {actor: id} or {actor:
|
|
317
|
+
if (r.actor !== "id" && r.actor !== "keys" && r.actor !== "kind")
|
|
318
|
+
v("R1", `${p}.conditions.${pc.id}`, `actor read ${JSON.stringify(r)} must be {actor: id}, {actor: keys} or {actor: kind}`);
|
|
311
319
|
}
|
|
312
320
|
else if (isObj(r)) {
|
|
313
321
|
if (!isStr(r.prop) || propClass(r.prop) === null || !Array.isArray(r.values) || r.values.length === 0)
|
|
@@ -429,7 +437,14 @@ export function check(ontology, facts) {
|
|
|
429
437
|
for (const [n, pv] of Object.entries(isObj(c.parameters) ? c.parameters : {})) {
|
|
430
438
|
if (!isObj(pv) || !isStr(pv.cite?.quote))
|
|
431
439
|
continue;
|
|
432
|
-
|
|
440
|
+
// A projected parameter is not a request field: its quote names the fields it is projected from.
|
|
441
|
+
const fields = projectedFields(pv);
|
|
442
|
+
if (fields !== null) {
|
|
443
|
+
for (const f of fields)
|
|
444
|
+
if (!IDENT(f).test(pv.cite.quote))
|
|
445
|
+
v("R2", `${cid}:${n}:${f}`, `parameter ${n} is projected from ${f}, which does not appear in its cite quote`, "projection_field_not_in_quote");
|
|
446
|
+
}
|
|
447
|
+
else if (!IDENT(n).test(pv.cite.quote))
|
|
433
448
|
v("R2", `${cid}:${n}`, `parameter ${n} does not appear in its cite quote`, "parameter_not_in_quote");
|
|
434
449
|
for (const x of Array.isArray(pv.values) ? pv.values.filter(isStr) : [])
|
|
435
450
|
if (!IDENT(x).test(pv.cite.quote))
|
package/dist/src/contract.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type RuleId = "R1" | "R2" | "R3" | "R4" | "R5" | "R6" | "R7" | "R8" | "R9" | "R10" | "R11" | "R12";
|
|
2
2
|
export declare const RULES: readonly RuleId[];
|
|
3
|
-
/** A name as a whole identifier: not preceded or followed by a letter, a digit or `_`. */
|
|
3
|
+
/** A name as a whole identifier: not preceded or followed by a letter, a digit or `_`. The empty string matches nothing. */
|
|
4
4
|
export declare const IDENT: (s: string) => RegExp;
|
|
5
5
|
export interface Source {
|
|
6
6
|
anchor: string;
|
package/dist/src/contract.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export const RULES = ["R1", "R2", "R3", "R4", "R5", "R6", "R7", "R8", "R9", "R10", "R11", "R12"];
|
|
2
|
-
/** A name as a whole identifier: not preceded or followed by a letter, a digit or `_`. */
|
|
3
|
-
export const IDENT = (s) => new RegExp(`(?<![A-Za-z0-9_])${s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![A-Za-z0-9_])`);
|
|
2
|
+
/** A name as a whole identifier: not preceded or followed by a letter, a digit or `_`. The empty string matches nothing. */
|
|
3
|
+
export const IDENT = (s) => (s === "" ? /(?!)/ : new RegExp(`(?<![A-Za-z0-9_])${s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![A-Za-z0-9_])`));
|
package/dist/src/evaluate.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Decision evaluation: evalValue / evalPredicate (two levels, Kleene logic) and decide (invocation layer, then decision table).
|
|
2
2
|
// Deterministic: no wall clock, no randomness, no global mutable state; `now` comes from the caller.
|
|
3
3
|
// Invalid input (ontology, snapshot, scope argument) throws; it is never a result.
|
|
4
|
-
import { checkPredicate, fitsType, own, RESERVED_NAMES, TIMESTAMP_MAX, TIMESTAMP_MIN } from "./expr.js";
|
|
4
|
+
import { checkPredicate, fitsType, own, projectedFields, RESERVED_NAMES, TIMESTAMP_MAX, TIMESTAMP_MIN } from "./expr.js";
|
|
5
5
|
// The fixed invocation result set of the format (defined in ./expr). decide never returns the first two (the idempotency
|
|
6
6
|
// envelope is not implemented here: it has no idempotency key).
|
|
7
7
|
export { INVOCATION_RESULTS } from "./expr.js";
|
|
@@ -127,7 +127,9 @@ function readRef(s, f, path) {
|
|
|
127
127
|
return s.env.actor.id;
|
|
128
128
|
if (prop === "keys")
|
|
129
129
|
return s.env.actor.keys;
|
|
130
|
-
|
|
130
|
+
if (prop === "kind")
|
|
131
|
+
return s.env.actor.kind;
|
|
132
|
+
throw new Error(`actor.${prop ?? ""} is not readable; use actor.id, actor.keys or actor.kind`);
|
|
131
133
|
}
|
|
132
134
|
const o = objVar(s, f, head);
|
|
133
135
|
if (prop === undefined) {
|
|
@@ -347,7 +349,9 @@ function admit(ont, actionId, bindings, actor) {
|
|
|
347
349
|
for (const [name, p] of Object.entries(params)) {
|
|
348
350
|
const v = own(bindings, name);
|
|
349
351
|
if (v === null || v === undefined) {
|
|
350
|
-
|
|
352
|
+
// A projected ref parameter is always given: null means the projection found nothing (not_found at the binding step).
|
|
353
|
+
const projected = projectedFields(p) !== null;
|
|
354
|
+
if (v === null ? p.optional === true || projected : p.optional === true && !projected)
|
|
351
355
|
continue;
|
|
352
356
|
return { layer: "invocation", result: "INVALID_BINDING", param: name, reason: "missing" };
|
|
353
357
|
}
|
|
@@ -380,6 +384,8 @@ function resolve(s, actionId) {
|
|
|
380
384
|
if (typeof elem === "string" || !("ref" in elem))
|
|
381
385
|
continue;
|
|
382
386
|
const v = own(s.env.bindings, name);
|
|
387
|
+
if (v === null && p.optional !== true && projectedFields(p) !== null)
|
|
388
|
+
return { layer: "invocation", result: "INVALID_BINDING", param: name, reason: "not_found" };
|
|
383
389
|
if (v === null || v === undefined)
|
|
384
390
|
continue;
|
|
385
391
|
for (const id of isSet ? v : [v]) {
|
package/dist/src/expr.d.ts
CHANGED
|
@@ -116,11 +116,16 @@ export type ActionType = {
|
|
|
116
116
|
type?: Type;
|
|
117
117
|
optional?: true;
|
|
118
118
|
values?: string[];
|
|
119
|
+
projectedFrom?: string[];
|
|
119
120
|
}>;
|
|
120
121
|
permission: Permission;
|
|
121
122
|
conditions: Condition[];
|
|
122
123
|
decision?: Decision;
|
|
123
124
|
};
|
|
125
|
+
export declare function projectedFields(p: {
|
|
126
|
+
type?: unknown;
|
|
127
|
+
projectedFrom?: unknown;
|
|
128
|
+
}): string[] | null;
|
|
124
129
|
export interface Stage1Ontology {
|
|
125
130
|
scope?: {
|
|
126
131
|
by: string;
|
package/dist/src/expr.js
CHANGED
|
@@ -11,6 +11,12 @@ export const DISPOSITION_CLASSES = ["rejected", "recorded", "pending", "applied"
|
|
|
11
11
|
// not be one of these names.
|
|
12
12
|
// Every invocation-layer result, SCOPE_DENIED included. SCOPE_DENIED is internal and never reported (SPEC section 9).
|
|
13
13
|
export const INVOCATION_RESULTS = ["REPLAYED", "IDEMPOTENCY_CONFLICT", "INVALID_BINDING", "SCOPE_DENIED", "PERMISSION_DENIED"];
|
|
14
|
+
// The distinct request fields of a projected parameter, or null: only a scalar {ref: T} parameter with at least one field is
|
|
15
|
+
// projected; on any other parameter projectedFrom has no effect.
|
|
16
|
+
export function projectedFields(p) {
|
|
17
|
+
const fs = Array.isArray(p.projectedFrom) ? [...new Set(p.projectedFrom.filter((f) => typeof f === "string"))] : [];
|
|
18
|
+
return isObj(p.type) && typeof p.type.ref === "string" && fs.length > 0 ? fs : null;
|
|
19
|
+
}
|
|
14
20
|
export class ExprError extends Error {
|
|
15
21
|
path;
|
|
16
22
|
constructor(path, msg) {
|
|
@@ -141,7 +147,9 @@ function refType(ont, path, ctx, at) {
|
|
|
141
147
|
return "id";
|
|
142
148
|
if (prop === "keys")
|
|
143
149
|
return { set: "string" };
|
|
144
|
-
|
|
150
|
+
if (prop === "kind")
|
|
151
|
+
return "enum";
|
|
152
|
+
throw new ExprError(at, `actor.${prop ?? ""} is not readable; use actor.id, actor.keys or actor.kind`);
|
|
145
153
|
}
|
|
146
154
|
if (head === "self" && ctx.self === undefined)
|
|
147
155
|
throw new ExprError(at, "self is valid only in a derived property expression");
|
package/dist/src/query.d.ts
CHANGED
package/dist/src/query.js
CHANGED
|
@@ -330,6 +330,7 @@ export function queryAction(ont, id) {
|
|
|
330
330
|
type: typeOf(map(p).type),
|
|
331
331
|
optional: map(p).optional === true,
|
|
332
332
|
values: Array.isArray(map(p).values) ? map(p).values.filter((x) => typeof x === "string") : null,
|
|
333
|
+
projectedFrom: Array.isArray(map(p).projectedFrom) ? map(p).projectedFrom.filter((x) => typeof x === "string") : null,
|
|
333
334
|
cite: citeOf(map(p).cite),
|
|
334
335
|
})),
|
|
335
336
|
conditions: conditionsOf(a).map((c) => ({
|
package/ontology.schema.json
CHANGED
|
@@ -673,7 +673,7 @@
|
|
|
673
673
|
"type": "object",
|
|
674
674
|
"propertyNames": { "minLength": 1 },
|
|
675
675
|
"additionalProperties": { "$ref": "#/$defs/parameter" },
|
|
676
|
-
"description": "Optional action parameters: parameter name → {cite}. Foundation: Palantir action parameters (\"Parameters are the inputs of an action type.\"). A condition reads a parameter as `{param: <name>}` or, in an expression, as `{ref: <name>}`. R1: must be a mapping; each entry carries a cite. R2: the cite quote must occur verbatim, and the parameter name must appear in it (R2 `parameter_not_in_quote`, key `<ID>:<name>`), and so must each of its `values` (R2 `parameter_value_not_in_quote`, key `<ID>:<name>:<value>`). A declared parameter that no condition reads is not a failure."
|
|
676
|
+
"description": "Optional action parameters: parameter name → {cite}. Foundation: Palantir action parameters (\"Parameters are the inputs of an action type.\"). A condition reads a parameter as `{param: <name>}` or, in an expression, as `{ref: <name>}`. R1: must be a mapping; each entry carries a cite. R2: the cite quote must occur verbatim, and the parameter name must appear in it (R2 `parameter_not_in_quote`, key `<ID>:<name>`), or, for a projected parameter (`type: {ref: <Object>}` with at least one `projectedFrom` field), each of its fields (R2 `projection_field_not_in_quote`, key `<ID>:<name>:<field>`), and so must each of its `values` (R2 `parameter_value_not_in_quote`, key `<ID>:<name>:<value>`). A declared parameter that no condition reads is not a failure."
|
|
677
677
|
},
|
|
678
678
|
"edits": {
|
|
679
679
|
"type": "array",
|
|
@@ -927,7 +927,7 @@
|
|
|
927
927
|
},
|
|
928
928
|
"expr": {
|
|
929
929
|
"$ref": "#/$defs/expr",
|
|
930
|
-
"description": "The claim as a predicate expression. R1: it type-checks in the action (parameters, `actor.id`, `actor.keys`, `now`, quantifier variables). R4: every literal must occur as a whole identifier in one of the `cites` quotes (else `literal_unsupported`, key `<ID>:<condition id>:<value>`)."
|
|
930
|
+
"description": "The claim as a predicate expression. R1: it type-checks in the action (parameters, `actor.id`, `actor.keys`, `actor.kind`, `now`, quantifier variables). R4: every literal must occur as a whole identifier in one of the `cites` quotes (else `literal_unsupported`, key `<ID>:<condition id>:<value>`)."
|
|
931
931
|
},
|
|
932
932
|
"unspecified": {
|
|
933
933
|
"type": "string",
|
|
@@ -936,7 +936,7 @@
|
|
|
936
936
|
},
|
|
937
937
|
"reads": {
|
|
938
938
|
"type": "array",
|
|
939
|
-
"description": "What the criterion reads. Items: `Object.prop` (a declared property), a derived id (a key of `derivedProperties`; its `reads` are checked instead), a value read `{prop, values}`, a parameter read `{param, values?}` (a key of this action's `parameters`) or an actor read `{actor: id | keys}`. R1: each item must resolve. R4: every property read, directly or through a derived id, must be class canonical (derived, policy and unknown fail R4 `precondition_reads_non_canonical`, key `<ID>:<Object.prop>`). A value read passes if the property is canonical, or if every value is in that property's `canonical_values.values`. Parameter and actor reads are not properties; R4 does not classify them. Each value of a parameter read must appear in one of this condition's cite quotes (R4 `parameter_value_unsupported`, key `<ID>:<criterion id>:<value>`).",
|
|
939
|
+
"description": "What the criterion reads. Items: `Object.prop` (a declared property), a derived id (a key of `derivedProperties`; its `reads` are checked instead), a value read `{prop, values}`, a parameter read `{param, values?}` (a key of this action's `parameters`) or an actor read `{actor: id | keys | kind}`. R1: each item must resolve. R4: every property read, directly or through a derived id, must be class canonical (derived, policy and unknown fail R4 `precondition_reads_non_canonical`, key `<ID>:<Object.prop>`). A value read passes if the property is canonical, or if every value is in that property's `canonical_values.values`. Parameter and actor reads are not properties; R4 does not classify them. Each value of a parameter read must appear in one of this condition's cite quotes (R4 `parameter_value_unsupported`, key `<ID>:<criterion id>:<value>`).",
|
|
940
940
|
"items": {
|
|
941
941
|
"oneOf": [
|
|
942
942
|
{
|
|
@@ -979,8 +979,8 @@
|
|
|
979
979
|
"type": "object",
|
|
980
980
|
"additionalProperties": false,
|
|
981
981
|
"required": ["actor"],
|
|
982
|
-
"description": "Actor read: the precondition reads the caller — `id` (its identity)
|
|
983
|
-
"properties": { "actor": { "enum": ["id", "keys"] } }
|
|
982
|
+
"description": "Actor read: the precondition reads the caller — `id` (its identity), `keys` (its effective permission keys, for example which `permission.any_of` alternative it satisfies) or `kind` (its actor kind, as in `principals`).",
|
|
983
|
+
"properties": { "actor": { "enum": ["id", "keys", "kind"] } }
|
|
984
984
|
}
|
|
985
985
|
]
|
|
986
986
|
}
|
|
@@ -1009,17 +1009,22 @@
|
|
|
1009
1009
|
"type": "object",
|
|
1010
1010
|
"additionalProperties": false,
|
|
1011
1011
|
"required": ["cite"],
|
|
1012
|
-
"description": "One action parameter. Foundation: Palantir action parameter. The name is the map key; the cite is the source text that names it. `type` is required when an expression reads the parameter; `optional: true` lets the caller omit it (it is then null). `values` is the value domain of an `enum` parameter (only with `type: enum`): a value outside it is `INVALID_BINDING` `type`; R1 checks its shape and that literals compared with the parameter are among them, R2 that each value appears in the cite quote.",
|
|
1012
|
+
"description": "One action parameter. Foundation: Palantir action parameter. The name is the map key; the cite is the source text that names it. `type` is required when an expression reads the parameter; `optional: true` lets the caller omit it (it is then null). `values` is the value domain of an `enum` parameter (only with `type: enum`): a value outside it is `INVALID_BINDING` `type`; R1 checks its shape and that literals compared with the parameter are among them, R2 that each value appears in the cite quote. `projectedFrom` (only with `type: {ref: <Object>}`) lists the request fields, an alternate key, that the project's projection resolves to this ref; each field is a declared parameter of the same action that is not projected (R1); on any other parameter R1 reports it and `decide` ignores it; the cite quote must name each field instead of the parameter name (R2 `projection_field_not_in_quote`); a null value of a required projected parameter is `INVALID_BINDING` `not_found`, and an absent one is `missing`, also with `optional: true` (SPEC section 9).",
|
|
1013
1013
|
"properties": {
|
|
1014
1014
|
"cite": { "$ref": "#/$defs/cite" },
|
|
1015
1015
|
"type": { "$ref": "#/$defs/valueType" },
|
|
1016
1016
|
"optional": { "const": true },
|
|
1017
|
-
"values": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "type": "string", "minLength": 1 } }
|
|
1017
|
+
"values": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "type": "string", "minLength": 1 } },
|
|
1018
|
+
"projectedFrom": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "type": "string", "minLength": 1 } }
|
|
1018
1019
|
},
|
|
1019
1020
|
"allOf": [
|
|
1020
1021
|
{
|
|
1021
1022
|
"if": { "required": ["values"] },
|
|
1022
1023
|
"then": { "required": ["type"], "properties": { "type": { "const": "enum" } } }
|
|
1024
|
+
},
|
|
1025
|
+
{
|
|
1026
|
+
"if": { "required": ["projectedFrom"] },
|
|
1027
|
+
"then": { "required": ["type"], "properties": { "type": { "type": "object", "required": ["ref"] } } }
|
|
1023
1028
|
}
|
|
1024
1029
|
]
|
|
1025
1030
|
},
|
|
@@ -1130,7 +1135,7 @@
|
|
|
1130
1135
|
"ref": {
|
|
1131
1136
|
"type": "string",
|
|
1132
1137
|
"minLength": 1,
|
|
1133
|
-
"description": "now, actor.id, actor.keys, a parameter, or <binding>.<property> (one dot)."
|
|
1138
|
+
"description": "now, actor.id, actor.keys, actor.kind, a parameter, or <binding>.<property> (one dot)."
|
|
1134
1139
|
},
|
|
1135
1140
|
"lit": {
|
|
1136
1141
|
"oneOf": [
|
package/package.json
CHANGED
package/vocab.ttl
CHANGED
|
@@ -324,7 +324,7 @@ ofv:param a rdf:Property ;
|
|
|
324
324
|
|
|
325
325
|
ofv:actor a rdf:Property ;
|
|
326
326
|
rdfs:label "actor" ;
|
|
327
|
-
rdfs:comment "The caller read by a criterion: id or
|
|
327
|
+
rdfs:comment "The caller read by a criterion: id, keys or kind. YAML: actionTypes.<ID>.conditions[].reads[].actor" ;
|
|
328
328
|
rdfs:isDefinedBy <https://w3id.org/avouch/v1/vocab> .
|
|
329
329
|
|
|
330
330
|
ofv:submissionCriteria a rdf:Property ;
|
|
@@ -475,7 +475,12 @@ ofv:set a rdf:Property ;
|
|
|
475
475
|
|
|
476
476
|
ofv:optional a rdf:Property ;
|
|
477
477
|
rdfs:label "optional" ;
|
|
478
|
-
rdfs:comment "The caller may leave the parameter out; it is then null. YAML: actionTypes.<ID>.parameters.<n>.optional" ;
|
|
478
|
+
rdfs:comment "The caller may leave the parameter out; it is then null. Not for a projected parameter: it is always given, and null is a value. YAML: actionTypes.<ID>.parameters.<n>.optional" ;
|
|
479
|
+
rdfs:isDefinedBy <https://w3id.org/avouch/v1/vocab> .
|
|
480
|
+
|
|
481
|
+
ofv:projectedFrom a rdf:Property ;
|
|
482
|
+
rdfs:label "projectedFrom" ;
|
|
483
|
+
rdfs:comment "Request fields (an alternate key; each a declared parameter of the same action that is not projected) that the project's projection resolves to this ref parameter. An optional projected parameter is always given: null when a field is missing or null, or when no object has the key. A required one is omitted when a field is missing or null, and null when no object has the key. YAML: actionTypes.<ID>.parameters.<n>.projectedFrom[]" ;
|
|
479
484
|
rdfs:isDefinedBy <https://w3id.org/avouch/v1/vocab> .
|
|
480
485
|
|
|
481
486
|
ofv:principals a rdf:Property ;
|