@githonllc/avouch 0.1.9 → 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 CHANGED
@@ -57,8 +57,8 @@ The definitions quoted in the "Palantir analog" column are short quotations from
57
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 |
58
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) |
59
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" | |
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 that the project resolves to this ref; it takes effect only on a `type: {ref: <Object>}` parameter with at least one field, and such a projected parameter is always given, also when optional (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}`); 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 |
62
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 |
63
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` |
64
64
  | `link_effects` | foundation + constraint | action rules: create / delete link | every incident link needs an entry, including `none` / `unspecified` (R6 sweep) |
@@ -115,10 +115,10 @@ 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` or `keys`), 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; 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); 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
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 |
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, 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 |
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 |
124
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 |
@@ -157,7 +157,7 @@ The table lists only the optional facts. Every rule may also read the required f
157
157
 
158
158
  ## 8. Versions
159
159
 
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.
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.
161
161
  During 0.x the v1 draft changes in place (`formatVersion` 1, `$id` v1); the rule above applies from the 1.0 semantic freeze.
162
162
 
163
163
  ## 9. Decision semantics
@@ -176,7 +176,7 @@ Every node is a single-key map. The reserved names `now`, `actor` and `self` are
176
176
 
177
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.
178
178
 
179
- 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.
180
180
 
181
181
  ### Types and values
182
182
 
@@ -230,7 +230,7 @@ Soundness contract: for each combination of null / non-null over the nullable re
230
230
  Nullability comes from the facts and the parameters; only this analysis treats unknown nullability as null:
231
231
 
232
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).
233
- - `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.
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
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.
235
235
 
236
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.
@@ -283,15 +283,15 @@ A request-shape check before `decide` (so before its shape step), permission and
283
283
 
284
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.
285
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. 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 a key field is missing from the request, the projection omits the binding; when no object has the key, it passes null; else it passes the id. Null is not the same as an absent binding:
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
287
 
288
288
  | Binding | Required parameter | Optional parameter |
289
289
  |---|---|---|
290
- | absent | `INVALID_BINDING` `missing` (shape) | `INVALID_BINDING` `missing` (shape) |
291
- | null | `INVALID_BINDING` `not_found` (binding and scope), as for an id that does not resolve | an ordinary value, for example "no other object has this key", which a condition tests with `isNull` |
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
292
  | id | bound as any ref parameter | bound as any ref parameter |
293
293
 
294
- An absent binding is a valid input (the request lacks a key field); when the runtime gives another result, `reconcile` shows the difference as a mismatch. 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.
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
295
 
296
296
  ### Decision table and INDETERMINATE
297
297
 
@@ -287,6 +287,11 @@ export function check(ontology, facts) {
287
287
  const pf = isObj(pv) ? pv.projectedFrom : undefined;
288
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
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`);
290
295
  }
291
296
  const pids = new Set();
292
297
  for (const [i, pc] of arr(c.conditions).entries()) {
@@ -309,8 +314,8 @@ export function check(ontology, facts) {
309
314
  v("R1", `${p}.conditions.${pc.id}`, `parameter read ${JSON.stringify(r)}: values must be a non-empty list of non-empty strings`);
310
315
  }
311
316
  else if (isObj(r) && "actor" in r) {
312
- if (r.actor !== "id" && r.actor !== "keys")
313
- v("R1", `${p}.conditions.${pc.id}`, `actor read ${JSON.stringify(r)} must be {actor: id} or {actor: keys}`);
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}`);
314
319
  }
315
320
  else if (isObj(r)) {
316
321
  if (!isStr(r.prop) || propClass(r.prop) === null || !Array.isArray(r.values) || r.values.length === 0)
@@ -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;
@@ -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_])`));
@@ -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
- throw new Error(`actor.${prop ?? ""} is not readable; use actor.id or actor.keys`);
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) {
package/dist/src/expr.js CHANGED
@@ -147,7 +147,9 @@ function refType(ont, path, ctx, at) {
147
147
  return "id";
148
148
  if (prop === "keys")
149
149
  return { set: "string" };
150
- throw new ExprError(at, `actor.${prop ?? ""} is not readable; use actor.id or actor.keys`);
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`);
151
153
  }
152
154
  if (head === "self" && ctx.self === undefined)
153
155
  throw new ExprError(at, "self is valid only in a derived property expression");
@@ -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) or `keys` (its effective permission keys, for example which `permission.any_of` alternative it satisfies).",
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,7 +1009,7 @@
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. `projectedFrom` (only with `type: {ref: <Object>}`) lists the request fields, an alternate key, that the project's projection resolves to this ref; 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).",
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" },
@@ -1135,7 +1135,7 @@
1135
1135
  "ref": {
1136
1136
  "type": "string",
1137
1137
  "minLength": 1,
1138
- "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)."
1139
1139
  },
1140
1140
  "lit": {
1141
1141
  "oneOf": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@githonllc/avouch",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
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 keys. YAML: actionTypes.<ID>.conditions[].reads[].actor" ;
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 ;
@@ -480,7 +480,7 @@ ofv:optional a rdf:Property ;
480
480
 
481
481
  ofv:projectedFrom a rdf:Property ;
482
482
  rdfs:label "projectedFrom" ;
483
- rdfs:comment "Request fields (an alternate key) that the project's projection resolves to this ref parameter. A projected parameter is always given; null means the projection found nothing. YAML: actionTypes.<ID>.parameters.<n>.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[]" ;
484
484
  rdfs:isDefinedBy <https://w3id.org/avouch/v1/vocab> .
485
485
 
486
486
  ofv:principals a rdf:Property ;