@prestack/thread 0.1.19 → 0.1.21

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.
@@ -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();
@@ -3,7 +3,7 @@ import { valuesIn } from '../../core/rows.ts'
3
3
  import { resolve } from 'node:path'
4
4
  import { argv, stderr, stdout } from 'node:process'
5
5
 
6
- import { exit } from '../../core/invoked.ts'
6
+ import { exit, invoked } from '../../core/invoked.ts'
7
7
  import { PUBLIC_ROLE as PUBLIC } from '../../core/actor.ts'
8
8
  import { ROOT, descend } from '../../core/descend.ts'
9
9
  import { forbids, runChecker, type Finding } from '../../core/finding.ts'
@@ -20,16 +20,6 @@ function stop(why: string): never {
20
20
 
21
21
  const CHECKING = argv.includes('--check')
22
22
 
23
- const pass = await descend()
24
-
25
- const stopped = [...pass.broken, ...pass.findings].filter(forbids)[0]
26
- if (stopped !== undefined) {
27
- stop(
28
- 'the descent is broken, so who may ask for what is only partly known, and a gate written from half of it ' +
29
- `would permit callers nobody granted — ${stopped.detail} (${stopped.file ?? ''})`,
30
- )
31
- }
32
-
33
23
  async function taken<T>(file: string, name: string, what: string): Promise<readonly T[]> {
34
24
  const at = inTree(findTree(), file)
35
25
  if (!existsSync(at)) stop(`${at} is where this app states ${what}, and it is not there`)
@@ -48,36 +38,6 @@ async function taken<T>(file: string, name: string, what: string): Promise<reado
48
38
  return list as readonly T[]
49
39
  }
50
40
 
51
- const grants = await taken<Grant>('access.ts', 'GRANTS', 'who may ask for what')
52
-
53
- const operations = [...woven(pass, types(pass))].map(([id]) => ({
54
- id,
55
- describes: pass.stated.get(id)?.describes ?? '',
56
- }))
57
-
58
- for (const found of [unanswerable(grants)]) {
59
- if (found.length > 0) stop(found.join('; '))
60
- }
61
-
62
- const twice = grantedTwice(grants)
63
- if (twice.length > 0) {
64
- stop(
65
- `${twice.map((one) => `\`${one}\``).join(', ')} ${twice.length === 1 ? 'is granted' : 'are granted'} more than once, ` +
66
- 'so two arms answer to one id and either may permit. One id, one policy — rename one of them, or delete the ' +
67
- 'arm that is not meant to stand',
68
- )
69
- }
70
-
71
- const open = opensToEverybody(grants)
72
- if (open.length > 0) {
73
- stop(
74
- `${open.map((one) => `\`${one.id}\``).join(', ')} names no roles or entitlements and states no condition that ` +
75
- 'reads the caller, so who may run this is left unsaid rather than decided. If it is meant to be open, say so: ' +
76
- `\`to: { roles: ['${PUBLIC}'] }\` is the role an unauthenticated caller already wears, and naming it puts ` +
77
- 'that decision in the sheet. Otherwise name the roles that may, or state a `when` saying whose row it is',
78
- )
79
- }
80
-
81
41
  const said = (held: unknown): string => JSON.stringify(held)
82
42
 
83
43
  const used = new Set<readonly string[]>()
@@ -156,7 +116,8 @@ const COMPARES = [
156
116
  '}',
157
117
  ]
158
118
 
159
- function permitting(): string {
119
+ export function permitting(grants: readonly Grant[]): string {
120
+ used.clear()
160
121
  const arms = grants.flatMap((one) => {
161
122
  const parts = [...wears(one), ...(one.when === undefined ? [] : [predicate(one.when, one)])]
162
123
  return [
@@ -197,28 +158,72 @@ function permitting(): string {
197
158
 
198
159
  const RUNS = 'thread permits'
199
160
 
200
- const permitted = resolve(ROOT, PERMITS_AT)
161
+ async function run(): Promise<void> {
162
+ const pass = await descend()
163
+
164
+ const stopped = [...pass.broken, ...pass.findings].filter(forbids)[0]
165
+ if (stopped !== undefined) {
166
+ stop(
167
+ 'the descent is broken, so who may ask for what is only partly known, and a gate written from half of it ' +
168
+ `would permit callers nobody granted — ${stopped.detail} (${stopped.file ?? ''})`,
169
+ )
170
+ }
171
+
172
+ const grants = await taken<Grant>('access.ts', 'GRANTS', 'who may ask for what')
201
173
 
202
- const content = permitting()
174
+ const operations = [...woven(pass, types(pass))].map(([id]) => ({
175
+ id,
176
+ describes: pass.stated.get(id)?.describes ?? '',
177
+ }))
203
178
 
204
- const count = `${String(grants.length)} grant(s) over ${String(operations.length)} operation(s)`
179
+ for (const found of [unanswerable(grants)]) {
180
+ if (found.length > 0) stop(found.join('; '))
181
+ }
205
182
 
206
- if (CHECKING) {
207
- const findings: Finding[] = []
208
- const current = existsSync(permitted) ? readFileSync(permitted, 'utf8') : ''
209
- if (current !== content) {
210
- findings.push({
211
- file: PERMITS_AT,
212
- detail: 'is not what this app grants',
213
- 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`,
214
- })
183
+ const twice = grantedTwice(grants)
184
+ if (twice.length > 0) {
185
+ stop(
186
+ `${twice.map((one) => `\`${one}\``).join(', ')} ${twice.length === 1 ? 'is granted' : 'are granted'} more than once, ` +
187
+ 'so two arms answer to one id and either may permit. One id, one policy — rename one of them, or delete the ' +
188
+ 'arm that is not meant to stand',
189
+ )
190
+ }
191
+
192
+ const open = opensToEverybody(grants)
193
+ if (open.length > 0) {
194
+ stop(
195
+ `${open.map((one) => `\`${one.id}\``).join(', ')} names no roles or entitlements and states no condition that ` +
196
+ 'reads the caller, so who may run this is left unsaid rather than decided. If it is meant to be open, say so: ' +
197
+ `\`to: { roles: ['${PUBLIC}'] }\` is the role an unauthenticated caller already wears, and naming it puts ` +
198
+ 'that decision in the sheet. Otherwise name the roles that may, or state a `when` saying whose row it is',
199
+ )
215
200
  }
216
- runChecker('permits', findings, count)
217
- exit(findings.length === 0 ? 0 : 1)
201
+
202
+ const permitted = resolve(ROOT, PERMITS_AT)
203
+
204
+ const content = permitting(grants)
205
+
206
+ const count = `${String(grants.length)} grant(s) over ${String(operations.length)} operation(s)`
207
+
208
+ if (CHECKING) {
209
+ const findings: Finding[] = []
210
+ const current = existsSync(permitted) ? readFileSync(permitted, 'utf8') : ''
211
+ if (current !== content) {
212
+ findings.push({
213
+ file: PERMITS_AT,
214
+ detail: 'is not what this app grants',
215
+ 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`,
216
+ })
217
+ }
218
+ runChecker('permits', findings, count)
219
+ exit(findings.length === 0 ? 0 : 1)
220
+ }
221
+
222
+ writeFileSync(permitted, content)
223
+ stdout.write(
224
+ `permits: wrote ${PERMITS_AT} — ${count}. Dispatch imports this check itself, so every operation passes it; ` +
225
+ '`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',
226
+ )
218
227
  }
219
228
 
220
- writeFileSync(permitted, content)
221
- stdout.write(
222
- `permits: wrote ${PERMITS_AT} — ${count}. Dispatch imports this check itself, so every operation passes it; ` +
223
- '`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',
224
- )
229
+ if (invoked(import.meta.url)) await run()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prestack/thread",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",