@prestack/thread 0.1.20 → 0.1.22

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.
Files changed (148) hide show
  1. package/CHANGELOG.md +193 -0
  2. package/dist/core/attach.js +21 -0
  3. package/dist/core/descend.d.ts +2 -0
  4. package/dist/core/descend.js +18 -3
  5. package/dist/core/generators/commit.js +55 -3
  6. package/dist/core/generators/dev.d.ts +1 -0
  7. package/dist/core/generators/dev.js +68 -0
  8. package/dist/core/rows.d.ts +5 -0
  9. package/dist/core/rows.js +45 -0
  10. package/dist/core/tree.d.ts +1 -0
  11. package/dist/core/tree.js +7 -1
  12. package/dist/derivation/generators/build.d.ts +3 -1
  13. package/dist/derivation/generators/build.js +103 -22
  14. package/dist/derivation/generators/release.js +74 -26
  15. package/dist/derivation/release.js +3 -3
  16. package/dist/integrations/cli.d.ts +3 -9
  17. package/dist/integrations/cli.js +96 -36
  18. package/dist/integrations/declared.d.ts +8 -0
  19. package/dist/integrations/declared.js +22 -0
  20. package/dist/integrations/pglite.js +13 -3
  21. package/dist/integrations/target.d.ts +4 -1
  22. package/dist/integrations/target.js +20 -6
  23. package/dist/integrity/attach.js +1 -5
  24. package/dist/integrity/generators/publish.js +2 -1
  25. package/dist/integrity/slopnators/sl_stale_path.js +10 -0
  26. package/dist/integrity/slopnators/sl_unlisted_program.js +2 -2
  27. package/dist/operations/access.js +3 -2
  28. package/dist/operations/capabilities.d.ts +2 -0
  29. package/dist/operations/capabilities.js +2 -0
  30. package/dist/operations/generators/capability.js +234 -24
  31. package/dist/operations/generators/compile.d.ts +21 -0
  32. package/dist/operations/generators/compile.js +151 -51
  33. package/dist/operations/generators/permits.d.ts +2 -1
  34. package/dist/operations/generators/permits.js +52 -47
  35. package/dist/operations/generators/seed.d.ts +3 -0
  36. package/dist/operations/generators/seed.js +25 -3
  37. package/dist/operations/nodes.d.ts +2 -2
  38. package/package.json +1 -1
  39. package/dist/core/actor.ts +0 -21
  40. package/dist/core/attach.ts +0 -613
  41. package/dist/core/chain.ts +0 -145
  42. package/dist/core/condition.ts +0 -227
  43. package/dist/core/descend.ts +0 -1173
  44. package/dist/core/errors/catalogue.ts +0 -314
  45. package/dist/core/errors/classes.ts +0 -51
  46. package/dist/core/errors/error.ts +0 -152
  47. package/dist/core/errors/namespaces.ts +0 -19
  48. package/dist/core/finding.ts +0 -37
  49. package/dist/core/generators/commit.ts +0 -157
  50. package/dist/core/init.ts +0 -222
  51. package/dist/core/invoked.ts +0 -52
  52. package/dist/core/round.ts +0 -74
  53. package/dist/core/rows.ts +0 -459
  54. package/dist/core/scope.ts +0 -20
  55. package/dist/core/thread.ts +0 -29
  56. package/dist/core/tree.ts +0 -90
  57. package/dist/core/types.ts +0 -351
  58. package/dist/derivation/attach.ts +0 -57
  59. package/dist/derivation/generators/build.ts +0 -371
  60. package/dist/derivation/generators/check.ts +0 -118
  61. package/dist/derivation/generators/determinism.ts +0 -145
  62. package/dist/derivation/generators/release.ts +0 -236
  63. package/dist/derivation/release.ts +0 -43
  64. package/dist/integrations/attach.ts +0 -187
  65. package/dist/integrations/cli.ts +0 -615
  66. package/dist/integrations/disk.ts +0 -466
  67. package/dist/integrations/generators/read.ts +0 -127
  68. package/dist/integrations/generators/vault.ts +0 -210
  69. package/dist/integrations/https.ts +0 -216
  70. package/dist/integrations/pglite.ts +0 -206
  71. package/dist/integrations/secret.ts +0 -54
  72. package/dist/integrations/target.ts +0 -656
  73. package/dist/integrations/vault.ts +0 -148
  74. package/dist/integrity/attach.ts +0 -150
  75. package/dist/integrity/check-threads.ts +0 -166
  76. package/dist/integrity/constraint.ts +0 -1246
  77. package/dist/integrity/generators/compiler.ts +0 -138
  78. package/dist/integrity/generators/gate.ts +0 -227
  79. package/dist/integrity/generators/issues.ts +0 -160
  80. package/dist/integrity/generators/publish.ts +0 -232
  81. package/dist/integrity/generators/trial.ts +0 -359
  82. package/dist/integrity/lint.ts +0 -214
  83. package/dist/integrity/lsp/bin.ts +0 -44
  84. package/dist/integrity/lsp/diagnostics.ts +0 -92
  85. package/dist/integrity/lsp/findings.ts +0 -63
  86. package/dist/integrity/lsp/lint.ts +0 -124
  87. package/dist/integrity/lsp/protocol.ts +0 -78
  88. package/dist/integrity/lsp/server.ts +0 -224
  89. package/dist/integrity/slopnator.ts +0 -324
  90. package/dist/integrity/slopnators/sl_broken_lineage.ts +0 -38
  91. package/dist/integrity/slopnators/sl_declared_elsewhere.ts +0 -60
  92. package/dist/integrity/slopnators/sl_derived_drift.ts +0 -101
  93. package/dist/integrity/slopnators/sl_door_in_a_node.ts +0 -250
  94. package/dist/integrity/slopnators/sl_hardcoded_style.ts +0 -55
  95. package/dist/integrity/slopnators/sl_inert_island.ts +0 -84
  96. package/dist/integrity/slopnators/sl_inert_slopnator.ts +0 -93
  97. package/dist/integrity/slopnators/sl_missing_row.ts +0 -140
  98. package/dist/integrity/slopnators/sl_no_barrel.ts +0 -48
  99. package/dist/integrity/slopnators/sl_no_child_process.ts +0 -100
  100. package/dist/integrity/slopnators/sl_no_comment.ts +0 -43
  101. package/dist/integrity/slopnators/sl_no_dispatch_bypass.ts +0 -276
  102. package/dist/integrity/slopnators/sl_no_import_cycle.ts +0 -66
  103. package/dist/integrity/slopnators/sl_no_such_command.ts +0 -81
  104. package/dist/integrity/slopnators/sl_only_takes_a_literal.ts +0 -63
  105. package/dist/integrity/slopnators/sl_outside_an_operation.ts +0 -25
  106. package/dist/integrity/slopnators/sl_screen_holds_only_declared.ts +0 -70
  107. package/dist/integrity/slopnators/sl_secret_literal.ts +0 -68
  108. package/dist/integrity/slopnators/sl_stale_path.ts +0 -178
  109. package/dist/integrity/slopnators/sl_twice_meant.ts +0 -57
  110. package/dist/integrity/slopnators/sl_twice_parented.ts +0 -82
  111. package/dist/integrity/slopnators/sl_undeclared_crossing.ts +0 -481
  112. package/dist/integrity/slopnators/sl_undeclared_destination.ts +0 -113
  113. package/dist/integrity/slopnators/sl_undeclared_element.ts +0 -44
  114. package/dist/integrity/slopnators/sl_undeclared_list.ts +0 -358
  115. package/dist/integrity/slopnators/sl_undeclared_node.ts +0 -75
  116. package/dist/integrity/slopnators/sl_undeclared_operation.ts +0 -257
  117. package/dist/integrity/slopnators/sl_undeclared_variant.ts +0 -152
  118. package/dist/integrity/slopnators/sl_undeclared_webhook.ts +0 -108
  119. package/dist/integrity/slopnators/sl_underived_route.ts +0 -72
  120. package/dist/integrity/slopnators/sl_undescribed.ts +0 -65
  121. package/dist/integrity/slopnators/sl_ungoverned_island.ts +0 -60
  122. package/dist/integrity/slopnators/sl_unlisted_program.ts +0 -46
  123. package/dist/integrity/slopnators/sl_unnamed_island.ts +0 -41
  124. package/dist/integrity/slopnators/sl_unpaired_member.ts +0 -139
  125. package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +0 -71
  126. package/dist/integrity/slopnators/sl_unprefixed_member.ts +0 -110
  127. package/dist/integrity/slopnators/sl_unthreaded.ts +0 -421
  128. package/dist/integrity/slopnators/sl_untranslated_key.ts +0 -140
  129. package/dist/integrity/slopnators/sl_unvaulted_secret.ts +0 -132
  130. package/dist/integrity/slopnators/sl_unversioned.ts +0 -52
  131. package/dist/integrity/sweep.ts +0 -395
  132. package/dist/knowledge/attach.ts +0 -327
  133. package/dist/knowledge/generators/docsgen.ts +0 -186
  134. package/dist/knowledge/generators/glossary.ts +0 -73
  135. package/dist/knowledge/glossary.ts +0 -6
  136. package/dist/operations/access.ts +0 -131
  137. package/dist/operations/attach.ts +0 -408
  138. package/dist/operations/capabilities.ts +0 -88
  139. package/dist/operations/dispatch.ts +0 -294
  140. package/dist/operations/error-results.ts +0 -31
  141. package/dist/operations/generators/capability.ts +0 -1054
  142. package/dist/operations/generators/compile.ts +0 -701
  143. package/dist/operations/generators/permits.ts +0 -224
  144. package/dist/operations/generators/seed.ts +0 -359
  145. package/dist/operations/generators/trial.ts +0 -666
  146. package/dist/operations/nodes.ts +0 -53
  147. package/dist/operations/operations.ts +0 -91
  148. package/dist/operations/result.ts +0 -28
@@ -2,7 +2,7 @@ import { existsSync, readFileSync, writeFileSync } from '../../integrations/disk
2
2
  import { valuesIn } from '../../core/rows.js';
3
3
  import { resolve } from 'node:path';
4
4
  import { argv, stderr, stdout } from 'node:process';
5
- import { exit } from '../../core/invoked.js';
5
+ import { exit, invoked } from '../../core/invoked.js';
6
6
  import { PUBLIC_ROLE as PUBLIC } from '../../core/actor.js';
7
7
  import { ROOT, descend } from '../../core/descend.js';
8
8
  import { forbids, runChecker } from '../../core/finding.js';
@@ -14,12 +14,6 @@ function stop(why) {
14
14
  exit(1);
15
15
  }
16
16
  const CHECKING = argv.includes('--check');
17
- const pass = await descend();
18
- const stopped = [...pass.broken, ...pass.findings].filter(forbids)[0];
19
- if (stopped !== undefined) {
20
- stop('the descent is broken, so who may ask for what is only partly known, and a gate written from half of it ' +
21
- `would permit callers nobody granted — ${stopped.detail} (${stopped.file ?? ''})`);
22
- }
23
17
  async function taken(file, name, what) {
24
18
  const at = inTree(findTree(), file);
25
19
  if (!existsSync(at))
@@ -38,28 +32,6 @@ async function taken(file, name, what) {
38
32
  stop(`${at} exports no \`${name}\` list, so nothing there states ${what}`);
39
33
  return list;
40
34
  }
41
- const grants = await taken('access.ts', 'GRANTS', 'who may ask for what');
42
- const operations = [...woven(pass, types(pass))].map(([id]) => ({
43
- id,
44
- describes: pass.stated.get(id)?.describes ?? '',
45
- }));
46
- for (const found of [unanswerable(grants)]) {
47
- if (found.length > 0)
48
- stop(found.join('; '));
49
- }
50
- const twice = grantedTwice(grants);
51
- if (twice.length > 0) {
52
- stop(`${twice.map((one) => `\`${one}\``).join(', ')} ${twice.length === 1 ? 'is granted' : 'are granted'} more than once, ` +
53
- 'so two arms answer to one id and either may permit. One id, one policy — rename one of them, or delete the ' +
54
- 'arm that is not meant to stand');
55
- }
56
- const open = opensToEverybody(grants);
57
- if (open.length > 0) {
58
- stop(`${open.map((one) => `\`${one.id}\``).join(', ')} names no roles or entitlements and states no condition that ` +
59
- 'reads the caller, so who may run this is left unsaid rather than decided. If it is meant to be open, say so: ' +
60
- `\`to: { roles: ['${PUBLIC}'] }\` is the role an unauthenticated caller already wears, and naming it puts ` +
61
- 'that decision in the sheet. Otherwise name the roles that may, or state a `when` saying whose row it is');
62
- }
63
35
  const said = (held) => JSON.stringify(held);
64
36
  const used = new Set();
65
37
  function reads(one, grant) {
@@ -133,7 +105,8 @@ const COMPARES = [
133
105
  ' return left >= other',
134
106
  '}',
135
107
  ];
136
- function permitting() {
108
+ export function permitting(grants) {
109
+ used.clear();
137
110
  const arms = grants.flatMap((one) => {
138
111
  const parts = [...wears(one), ...(one.when === undefined ? [] : [predicate(one.when, one)])];
139
112
  return [
@@ -168,22 +141,54 @@ function permitting() {
168
141
  ].join('\n');
169
142
  }
170
143
  const RUNS = 'thread permits';
171
- const permitted = resolve(ROOT, PERMITS_AT);
172
- const content = permitting();
173
- const count = `${String(grants.length)} grant(s) over ${String(operations.length)} operation(s)`;
174
- if (CHECKING) {
175
- const findings = [];
176
- const current = existsSync(permitted) ? readFileSync(permitted, 'utf8') : '';
177
- if (current !== content) {
178
- findings.push({
179
- file: PERMITS_AT,
180
- detail: 'is not what this app grants',
181
- instead: `run \`${RUNS}\` and commit what it writes. Never edit this file — a branch here exists because a grant was declared, and an edit widens or narrows access nobody agreed to`,
182
- });
144
+ async function run() {
145
+ const pass = await descend();
146
+ const stopped = [...pass.broken, ...pass.findings].filter(forbids)[0];
147
+ if (stopped !== undefined) {
148
+ stop('the descent is broken, so who may ask for what is only partly known, and a gate written from half of it ' +
149
+ `would permit callers nobody granted — ${stopped.detail} (${stopped.file ?? ''})`);
150
+ }
151
+ const grants = await taken('access.ts', 'GRANTS', 'who may ask for what');
152
+ const operations = [...woven(pass, types(pass))].map(([id]) => ({
153
+ id,
154
+ describes: pass.stated.get(id)?.describes ?? '',
155
+ }));
156
+ for (const found of [unanswerable(grants)]) {
157
+ if (found.length > 0)
158
+ stop(found.join('; '));
159
+ }
160
+ const twice = grantedTwice(grants);
161
+ if (twice.length > 0) {
162
+ stop(`${twice.map((one) => `\`${one}\``).join(', ')} ${twice.length === 1 ? 'is granted' : 'are granted'} more than once, ` +
163
+ 'so two arms answer to one id and either may permit. One id, one policy — rename one of them, or delete the ' +
164
+ 'arm that is not meant to stand');
165
+ }
166
+ const open = opensToEverybody(grants);
167
+ if (open.length > 0) {
168
+ stop(`${open.map((one) => `\`${one.id}\``).join(', ')} names no roles or entitlements and states no condition that ` +
169
+ 'reads the caller, so who may run this is left unsaid rather than decided. If it is meant to be open, say so: ' +
170
+ `\`to: { roles: ['${PUBLIC}'] }\` is the role an unauthenticated caller already wears, and naming it puts ` +
171
+ 'that decision in the sheet. Otherwise name the roles that may, or state a `when` saying whose row it is');
172
+ }
173
+ const permitted = resolve(ROOT, PERMITS_AT);
174
+ const content = permitting(grants);
175
+ const count = `${String(grants.length)} grant(s) over ${String(operations.length)} operation(s)`;
176
+ if (CHECKING) {
177
+ const findings = [];
178
+ const current = existsSync(permitted) ? readFileSync(permitted, 'utf8') : '';
179
+ if (current !== content) {
180
+ findings.push({
181
+ file: PERMITS_AT,
182
+ detail: 'is not what this app grants',
183
+ instead: `run \`${RUNS}\` and commit what it writes. Never edit this file — a branch here exists because a grant was declared, and an edit widens or narrows access nobody agreed to`,
184
+ });
185
+ }
186
+ runChecker('permits', findings, count);
187
+ exit(findings.length === 0 ? 0 : 1);
183
188
  }
184
- runChecker('permits', findings, count);
185
- exit(findings.length === 0 ? 0 : 1);
189
+ writeFileSync(permitted, content);
190
+ stdout.write(`permits: wrote ${PERMITS_AT} — ${count}. Dispatch imports this check itself, so every operation passes it; ` +
191
+ '`thread grant` adds a grant, and a refusal no grant can say is an invariant on the entity, or a rule from `thread create rule`.\n');
186
192
  }
187
- writeFileSync(permitted, content);
188
- stdout.write(`permits: wrote ${PERMITS_AT} — ${count}. Dispatch imports this check itself, so every operation passes it; ` +
189
- '`thread grant` adds a grant, and a refusal no grant can say is an invariant on the entity, or a rule from `thread create rule`.\n');
193
+ if (invoked(import.meta.url))
194
+ await run();
@@ -9,10 +9,13 @@ interface Woven {
9
9
  readonly nodes: readonly Row[];
10
10
  readonly takes: readonly Row[];
11
11
  readonly body?: string;
12
+ readonly within: ReadonlyMap<string, readonly Row[]>;
12
13
  readonly requires: readonly string[];
13
14
  readonly stray: readonly string[];
14
15
  readonly timeout?: number;
15
16
  }
17
+ export declare const HOLDING: Readonly<Record<string, (first: string) => string>>;
18
+ export declare const stepsOf: (one: Woven) => readonly Row[];
16
19
  export declare function operations(pass: Pass, known: ReadonlySet<string>): ReadonlyMap<string, Woven>;
17
20
  export declare const problem: (id: string, one: Woven) => string | undefined;
18
21
  export declare function blockIn(text: string): string | undefined;
@@ -50,6 +50,11 @@ export function builtFrom(row, known) {
50
50
  const found = (row.requires ?? []).filter((one) => known.has(one));
51
51
  return found.length === 1 ? found[0] : undefined;
52
52
  }
53
+ export const HOLDING = {
54
+ nd_repeat: () => '1',
55
+ nd_branch: (first) => `async () => ${quoted(first)}`,
56
+ };
57
+ export const stepsOf = (one) => [...one.nodes, ...[...one.within.values()].flat()];
53
58
  const reaches = (pass, one) => pass.stated.has(one) && !under(pass, one, NODES_ID);
54
59
  const waiting = (held) => held !== undefined && ts.isNumericLiteral(held) ? Number(held.text.replace(/_/g, '')) : undefined;
55
60
  export function operations(pass, known) {
@@ -64,6 +69,7 @@ export function operations(pass, known) {
64
69
  nodes: [],
65
70
  takes: [],
66
71
  body: undefined,
72
+ within: new Map(),
67
73
  wants: new Set(stated?.takes ?? []),
68
74
  requires: asked.filter((one) => reaches(pass, one)),
69
75
  stray: asked.filter((one) => !reaches(pass, one)),
@@ -72,8 +78,17 @@ export function operations(pass, known) {
72
78
  found.set(id, made);
73
79
  return made;
74
80
  };
81
+ const steps = new Map();
75
82
  for (const [at, threadings] of pass.threaded) {
76
83
  for (const one of threadings) {
84
+ const holder = steps.get(one.by);
85
+ if (holder !== undefined && one.list === 'seeds') {
86
+ const within = held(holder).within;
87
+ within.set(one.by, [...(within.get(one.by) ?? []), one.row]);
88
+ if (one.row.id !== undefined)
89
+ steps.set(one.row.id, holder);
90
+ continue;
91
+ }
77
92
  if (!isOperation(pass, one.by))
78
93
  continue;
79
94
  if (one.list === 'derives' && one.row.by === COMPILES)
@@ -82,8 +97,11 @@ export function operations(pass, known) {
82
97
  const mine = held(one.by);
83
98
  if (mine.wants.has(one.row.id ?? ''))
84
99
  mine.takes.push(one.row);
85
- else
100
+ else {
86
101
  mine.nodes.push(one.row);
102
+ if (one.row.id !== undefined)
103
+ steps.set(one.row.id, one.by);
104
+ }
87
105
  }
88
106
  }
89
107
  }
@@ -166,14 +184,18 @@ function planned(bodies) {
166
184
  for (const row of one.takes)
167
185
  if (row.at !== undefined)
168
186
  found.push({ at: row.at, derived: false, text: takesFile(row.at) });
169
- for (const row of one.nodes) {
187
+ for (const row of stepsOf(one)) {
170
188
  const from = builtFrom(row, known);
171
189
  const takes = one.takes[0]?.at;
172
190
  if (row.at === undefined || from === undefined || takes === undefined)
173
191
  continue;
174
192
  const path = resolve(ROOT, row.at);
175
193
  const held = existsSync(path) ? readFileSync(path, 'utf8') : undefined;
176
- const block = bodies.get(row.at) ?? (held === undefined ? undefined : blockIn(held));
194
+ const first = one.within.get(String(row.id))?.[0]?.id;
195
+ const fresh = first === undefined ? undefined : HOLDING[from]?.(first);
196
+ const written = held === undefined ? undefined : blockIn(held);
197
+ const untouched = held === undefined || written === scaffold(from, String(row.id));
198
+ const block = bodies.get(row.at) ?? (fresh !== undefined && untouched ? fresh : written);
177
199
  if (held !== undefined && block === undefined) {
178
200
  found.push({
179
201
  at: row.at,
@@ -14,7 +14,7 @@ export declare const nd_read: <I, R extends string = never>(block: (opened: Read
14
14
  export declare const nd_action: <I, R extends string = never>(block: (opened: Opened<R>, held: Held<I>) => Promise<unknown>) => {
15
15
  type: 'nd_action';
16
16
  };
17
- export declare const nd_branch: <I, R extends string = never>(choose: (opened: Reads<Opened<R>>, held: Held<I>) => Promise<string>, ways: Readonly<Record<string, (opened: Opened<R>, held: Held<I>) => Promise<unknown>>>) => {
17
+ export declare const nd_branch: <I, R extends string = never>(choose: (opened: Reads<Opened<R>>, held: Held<I>) => Promise<string>, ways?: Readonly<Record<string, (opened: Opened<R>, held: Held<I>) => Promise<unknown>>>) => {
18
18
  type: 'nd_branch';
19
19
  };
20
20
  export declare const nd_each: <I, R extends string = never>(over: (held: Held<I>) => readonly unknown[], block: (item: unknown, opened: Opened<R>, held: Held<I>) => Promise<unknown>) => {
@@ -23,7 +23,7 @@ export declare const nd_each: <I, R extends string = never>(over: (held: Held<I>
23
23
  export declare const nd_parallel: <I, R extends string = never>(blocks: readonly ((opened: Opened<R>, held: Held<I>) => Promise<unknown>)[]) => {
24
24
  type: 'nd_parallel';
25
25
  };
26
- export declare const nd_repeat: <I, R extends string = never>(ceiling: number, block: (pass: number, carried: unknown, opened: Opened<R>, held: Held<I>) => Promise<{
26
+ export declare const nd_repeat: <I, R extends string = never>(ceiling: number, block?: (pass: number, carried: unknown, opened: Opened<R>, held: Held<I>) => Promise<{
27
27
  readonly done: boolean;
28
28
  readonly value: unknown;
29
29
  }>) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prestack/thread",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -1,21 +0,0 @@
1
- export type Env = 'sandbox' | 'production'
2
-
3
- export const ENVIRONMENT = [
4
- ['run', 'x-request-id', 'crypto.randomUUID()'],
5
- ['address', 'x-forwarded-for', null],
6
- ['device', 'user-agent', null],
7
- ['replay', 'idempotency-key', null],
8
- ['moment', null, 'new Date().toISOString()'],
9
- ] as const
10
-
11
- export type Environment = { readonly [K in (typeof ENVIRONMENT)[number][0]]?: string }
12
-
13
- export type Actor = Environment & {
14
- readonly id: string | null
15
- readonly roles: readonly string[]
16
- readonly entitlements: readonly string[]
17
- }
18
-
19
- export const PUBLIC_ROLE = 'role_public'
20
-
21
- export const ANONYMOUS: Actor = { id: null, roles: [PUBLIC_ROLE], entitlements: [] }