@prestack/thread 0.0.1
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/CHANGELOG.md +192 -0
- package/dist/attach.d.ts +1 -0
- package/dist/attach.js +1 -0
- package/dist/core/actor.d.ts +12 -0
- package/dist/core/actor.js +8 -0
- package/dist/core/asking.d.ts +180 -0
- package/dist/core/asking.js +1109 -0
- package/dist/core/attach.d.ts +54 -0
- package/dist/core/attach.js +510 -0
- package/dist/core/chain.d.ts +16 -0
- package/dist/core/chain.js +114 -0
- package/dist/core/cli.d.ts +2 -0
- package/dist/core/cli.js +1682 -0
- package/dist/core/condition.d.ts +52 -0
- package/dist/core/condition.js +198 -0
- package/dist/core/descend.d.ts +81 -0
- package/dist/core/descend.js +879 -0
- package/dist/core/finding.d.ts +12 -0
- package/dist/core/finding.js +23 -0
- package/dist/core/generators/meta.d.ts +2 -0
- package/dist/core/generators/meta.js +823 -0
- package/dist/core/init.d.ts +15 -0
- package/dist/core/init.js +134 -0
- package/dist/core/invoked.d.ts +9 -0
- package/dist/core/invoked.js +41 -0
- package/dist/core/round.d.ts +1 -0
- package/dist/core/round.js +57 -0
- package/dist/core/rows.d.ts +26 -0
- package/dist/core/rows.js +354 -0
- package/dist/core/scope.d.ts +6 -0
- package/dist/core/scope.js +12 -0
- package/dist/core/thread.d.ts +26 -0
- package/dist/core/thread.js +5 -0
- package/dist/core/tree.d.ts +12 -0
- package/dist/core/tree.js +56 -0
- package/dist/core/types.d.ts +49 -0
- package/dist/core/types.js +311 -0
- package/dist/derivation/attach.d.ts +2 -0
- package/dist/derivation/attach.js +61 -0
- package/dist/derivation/generators/build.d.ts +16 -0
- package/dist/derivation/generators/build.js +217 -0
- package/dist/derivation/generators/check.d.ts +18 -0
- package/dist/derivation/generators/check.js +78 -0
- package/dist/derivation/generators/derivation.d.ts +3 -0
- package/dist/derivation/generators/derivation.js +273 -0
- package/dist/derivation/generators/determinism.d.ts +20 -0
- package/dist/derivation/generators/determinism.js +110 -0
- package/dist/derivation/generators/release.d.ts +3 -0
- package/dist/derivation/generators/release.js +183 -0
- package/dist/derivation/makers/release.d.ts +1 -0
- package/dist/derivation/makers/release.js +107 -0
- package/dist/derivation/release.d.ts +10 -0
- package/dist/derivation/release.js +12 -0
- package/dist/integrations/attach.d.ts +6 -0
- package/dist/integrations/attach.js +177 -0
- package/dist/integrations/generators/read.d.ts +1 -0
- package/dist/integrations/generators/read.js +92 -0
- package/dist/integrations/generators/sdk.d.ts +1 -0
- package/dist/integrations/generators/sdk.js +148 -0
- package/dist/integrations/generators/secret.d.ts +1 -0
- package/dist/integrations/generators/secret.js +233 -0
- package/dist/integrations/generators/target.d.ts +1 -0
- package/dist/integrations/generators/target.js +765 -0
- package/dist/integrations/generators/vault.d.ts +1 -0
- package/dist/integrations/generators/vault.js +164 -0
- package/dist/integrations/sdk.d.ts +6 -0
- package/dist/integrations/sdk.js +151 -0
- package/dist/integrations/secret.d.ts +16 -0
- package/dist/integrations/secret.js +30 -0
- package/dist/integrations/target.d.ts +49 -0
- package/dist/integrations/target.js +225 -0
- package/dist/integrations/vault.d.ts +12 -0
- package/dist/integrations/vault.js +104 -0
- package/dist/integrity/attach.d.ts +2 -0
- package/dist/integrity/attach.js +213 -0
- package/dist/integrity/check-threads.d.ts +10 -0
- package/dist/integrity/check-threads.js +153 -0
- package/dist/integrity/constraint.d.ts +16 -0
- package/dist/integrity/constraint.js +1105 -0
- package/dist/integrity/generators/compiler.d.ts +1 -0
- package/dist/integrity/generators/compiler.js +130 -0
- package/dist/integrity/generators/gate.d.ts +1 -0
- package/dist/integrity/generators/gate.js +172 -0
- package/dist/integrity/generators/issues.d.ts +1 -0
- package/dist/integrity/generators/issues.js +100 -0
- package/dist/integrity/generators/publish.d.ts +1 -0
- package/dist/integrity/generators/publish.js +186 -0
- package/dist/integrity/generators/rule.d.ts +2 -0
- package/dist/integrity/generators/rule.js +213 -0
- package/dist/integrity/generators/seal.d.ts +4 -0
- package/dist/integrity/generators/seal.js +101 -0
- package/dist/integrity/generators/trial.d.ts +39 -0
- package/dist/integrity/generators/trial.js +254 -0
- package/dist/integrity/lint.d.ts +50 -0
- package/dist/integrity/lint.js +161 -0
- package/dist/integrity/lsp/bin.d.ts +2 -0
- package/dist/integrity/lsp/bin.js +35 -0
- package/dist/integrity/lsp/diagnostics.d.ts +25 -0
- package/dist/integrity/lsp/diagnostics.js +57 -0
- package/dist/integrity/lsp/findings.d.ts +20 -0
- package/dist/integrity/lsp/findings.js +39 -0
- package/dist/integrity/lsp/lint.d.ts +7 -0
- package/dist/integrity/lsp/lint.js +95 -0
- package/dist/integrity/lsp/protocol.d.ts +20 -0
- package/dist/integrity/lsp/protocol.js +57 -0
- package/dist/integrity/lsp/server.d.ts +15 -0
- package/dist/integrity/lsp/server.js +171 -0
- package/dist/integrity/sealed.d.ts +1 -0
- package/dist/integrity/sealed.js +56 -0
- package/dist/integrity/slopnator.d.ts +90 -0
- package/dist/integrity/slopnator.js +267 -0
- package/dist/integrity/slopnators/sl_broken_lineage.d.ts +2 -0
- package/dist/integrity/slopnators/sl_broken_lineage.js +35 -0
- package/dist/integrity/slopnators/sl_declared_elsewhere.d.ts +2 -0
- package/dist/integrity/slopnators/sl_declared_elsewhere.js +33 -0
- package/dist/integrity/slopnators/sl_derived_drift.d.ts +8 -0
- package/dist/integrity/slopnators/sl_derived_drift.js +91 -0
- package/dist/integrity/slopnators/sl_hardcoded_style.d.ts +2 -0
- package/dist/integrity/slopnators/sl_hardcoded_style.js +46 -0
- package/dist/integrity/slopnators/sl_inert_island.d.ts +2 -0
- package/dist/integrity/slopnators/sl_inert_island.js +85 -0
- package/dist/integrity/slopnators/sl_inert_slopnator.d.ts +2 -0
- package/dist/integrity/slopnators/sl_inert_slopnator.js +76 -0
- package/dist/integrity/slopnators/sl_missing_row.d.ts +2 -0
- package/dist/integrity/slopnators/sl_missing_row.js +180 -0
- package/dist/integrity/slopnators/sl_no_barrel.d.ts +2 -0
- package/dist/integrity/slopnators/sl_no_barrel.js +41 -0
- package/dist/integrity/slopnators/sl_no_child_process.d.ts +2 -0
- package/dist/integrity/slopnators/sl_no_child_process.js +61 -0
- package/dist/integrity/slopnators/sl_no_comment.d.ts +2 -0
- package/dist/integrity/slopnators/sl_no_comment.js +41 -0
- package/dist/integrity/slopnators/sl_no_dispatch_bypass.d.ts +2 -0
- package/dist/integrity/slopnators/sl_no_dispatch_bypass.js +301 -0
- package/dist/integrity/slopnators/sl_no_import_cycle.d.ts +2 -0
- package/dist/integrity/slopnators/sl_no_import_cycle.js +42 -0
- package/dist/integrity/slopnators/sl_no_such_command.d.ts +2 -0
- package/dist/integrity/slopnators/sl_no_such_command.js +55 -0
- package/dist/integrity/slopnators/sl_only_takes_a_literal.d.ts +2 -0
- package/dist/integrity/slopnators/sl_only_takes_a_literal.js +59 -0
- package/dist/integrity/slopnators/sl_secret_literal.d.ts +2 -0
- package/dist/integrity/slopnators/sl_secret_literal.js +89 -0
- package/dist/integrity/slopnators/sl_stale_path.d.ts +2 -0
- package/dist/integrity/slopnators/sl_stale_path.js +92 -0
- package/dist/integrity/slopnators/sl_twice_meant.d.ts +2 -0
- package/dist/integrity/slopnators/sl_twice_meant.js +30 -0
- package/dist/integrity/slopnators/sl_twice_parented.d.ts +2 -0
- package/dist/integrity/slopnators/sl_twice_parented.js +34 -0
- package/dist/integrity/slopnators/sl_undeclared_crossing.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undeclared_crossing.js +78 -0
- package/dist/integrity/slopnators/sl_undeclared_destination.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undeclared_destination.js +56 -0
- package/dist/integrity/slopnators/sl_undeclared_element.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undeclared_element.js +34 -0
- package/dist/integrity/slopnators/sl_undeclared_node.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undeclared_node.js +59 -0
- package/dist/integrity/slopnators/sl_undeclared_operation.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undeclared_operation.js +120 -0
- package/dist/integrity/slopnators/sl_undeclared_variant.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undeclared_variant.js +117 -0
- package/dist/integrity/slopnators/sl_undescribed.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undescribed.js +54 -0
- package/dist/integrity/slopnators/sl_ungoverned_island.d.ts +2 -0
- package/dist/integrity/slopnators/sl_ungoverned_island.js +58 -0
- package/dist/integrity/slopnators/sl_unkept_rows.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unkept_rows.js +104 -0
- package/dist/integrity/slopnators/sl_unnamed_island.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unnamed_island.js +37 -0
- package/dist/integrity/slopnators/sl_unpaired_member.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unpaired_member.js +114 -0
- package/dist/integrity/slopnators/sl_unpaired_slopnator.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unpaired_slopnator.js +60 -0
- package/dist/integrity/slopnators/sl_unprefixed_member.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unprefixed_member.js +126 -0
- package/dist/integrity/slopnators/sl_unseamed_reach.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unseamed_reach.js +73 -0
- package/dist/integrity/slopnators/sl_unthreaded.d.ts +56 -0
- package/dist/integrity/slopnators/sl_unthreaded.js +352 -0
- package/dist/integrity/slopnators/sl_untranslated_key.d.ts +2 -0
- package/dist/integrity/slopnators/sl_untranslated_key.js +157 -0
- package/dist/integrity/slopnators/sl_unversioned.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unversioned.js +31 -0
- package/dist/integrity/sweep.d.ts +24 -0
- package/dist/integrity/sweep.js +264 -0
- package/dist/knowledge/attach.d.ts +2 -0
- package/dist/knowledge/attach.js +339 -0
- package/dist/knowledge/generators/docsgen.d.ts +1 -0
- package/dist/knowledge/generators/docsgen.js +145 -0
- package/dist/knowledge/generators/glossary.d.ts +1 -0
- package/dist/knowledge/generators/glossary.js +56 -0
- package/dist/knowledge/generators/skills.d.ts +1 -0
- package/dist/knowledge/generators/skills.js +222 -0
- package/dist/knowledge/glossary.d.ts +6 -0
- package/dist/knowledge/glossary.js +1 -0
- package/dist/operations/access.d.ts +27 -0
- package/dist/operations/access.js +61 -0
- package/dist/operations/attach.d.ts +6 -0
- package/dist/operations/attach.js +414 -0
- package/dist/operations/capabilities.d.ts +37 -0
- package/dist/operations/capabilities.js +35 -0
- package/dist/operations/cost.d.ts +14 -0
- package/dist/operations/cost.js +37 -0
- package/dist/operations/dispatch.d.ts +14 -0
- package/dist/operations/dispatch.js +327 -0
- package/dist/operations/error-results.d.ts +4 -0
- package/dist/operations/error-results.js +25 -0
- package/dist/operations/errors/catalog.d.ts +58 -0
- package/dist/operations/errors/catalog.js +91 -0
- package/dist/operations/errors/classes.d.ts +73 -0
- package/dist/operations/errors/classes.js +22 -0
- package/dist/operations/errors/errors.d.ts +52 -0
- package/dist/operations/errors/errors.js +257 -0
- package/dist/operations/generators/capability.d.ts +5 -0
- package/dist/operations/generators/capability.js +603 -0
- package/dist/operations/generators/compile.d.ts +2 -0
- package/dist/operations/generators/compile.js +541 -0
- package/dist/operations/generators/grant.d.ts +1 -0
- package/dist/operations/generators/grant.js +267 -0
- package/dist/operations/generators/operation.d.ts +1 -0
- package/dist/operations/generators/operation.js +811 -0
- package/dist/operations/generators/permits.d.ts +1 -0
- package/dist/operations/generators/permits.js +196 -0
- package/dist/operations/generators/seed.d.ts +20 -0
- package/dist/operations/generators/seed.js +285 -0
- package/dist/operations/generators/trial.d.ts +53 -0
- package/dist/operations/generators/trial.js +376 -0
- package/dist/operations/http.d.ts +30 -0
- package/dist/operations/http.js +121 -0
- package/dist/operations/makers/capability.d.ts +1 -0
- package/dist/operations/makers/capability.js +117 -0
- package/dist/operations/makers/trial.d.ts +1 -0
- package/dist/operations/makers/trial.js +413 -0
- package/dist/operations/memory.d.ts +104 -0
- package/dist/operations/memory.js +365 -0
- package/dist/operations/metering.d.ts +55 -0
- package/dist/operations/metering.js +247 -0
- package/dist/operations/nodes.d.ts +35 -0
- package/dist/operations/nodes.js +15 -0
- package/dist/operations/operations.d.ts +18 -0
- package/dist/operations/operations.js +5 -0
- package/dist/operations/queue-policy.d.ts +3 -0
- package/dist/operations/queue-policy.js +8 -0
- package/dist/operations/result.d.ts +19 -0
- package/dist/operations/result.js +10 -0
- package/dist/operations/seams.d.ts +239 -0
- package/dist/operations/seams.js +45 -0
- package/dist/operations/store.d.ts +14 -0
- package/dist/operations/store.js +1 -0
- package/package.json +16 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
10
|
+
import { dirname, resolve } from 'node:path';
|
|
11
|
+
import { argv, stderr, stdout } from 'node:process';
|
|
12
|
+
import { pathToFileURL } from 'node:url';
|
|
13
|
+
import { exit } from "../../core/invoked.js";
|
|
14
|
+
import { PUBLIC_ROLE as PUBLIC } from "../../core/actor.js";
|
|
15
|
+
import { ROOT, descend } from "../../core/descend.js";
|
|
16
|
+
import { forbids, runChecker } from "../../core/finding.js";
|
|
17
|
+
import { findTree, inTree } from "../../core/tree.js";
|
|
18
|
+
import { PERMITS_AT, grantedTwice, opensToEverybody, unanswerable } from "../access.js";
|
|
19
|
+
import { DECLARED_SEAMS_AT } from "../capabilities.js";
|
|
20
|
+
import { operations as woven, runtime, types } from "./seed.js";
|
|
21
|
+
function stop(why) {
|
|
22
|
+
stderr.write(`permits: ${why}\n`);
|
|
23
|
+
exit(1);
|
|
24
|
+
}
|
|
25
|
+
const CHECKING = argv.includes('--check');
|
|
26
|
+
const pass = await descend();
|
|
27
|
+
const stopped = [...pass.broken, ...pass.findings].filter(forbids)[0];
|
|
28
|
+
if (stopped !== undefined) {
|
|
29
|
+
stop('the descent is broken, so who may ask for what is only partly known, and a gate written from half of it ' +
|
|
30
|
+
`would permit callers nobody granted — ${stopped.detail} (${stopped.file ?? ''})`);
|
|
31
|
+
}
|
|
32
|
+
async function taken(file, name, what) {
|
|
33
|
+
const at = inTree(findTree(), file);
|
|
34
|
+
if (!existsSync(at))
|
|
35
|
+
stop(`${at} is where this app states ${what}, and it is not there`);
|
|
36
|
+
let held;
|
|
37
|
+
try {
|
|
38
|
+
held = (await import(__rewriteRelativeImportExtension(pathToFileURL(at).href)));
|
|
39
|
+
}
|
|
40
|
+
catch (thrown) {
|
|
41
|
+
stop(`${at} could not be read: ${thrown instanceof Error ? thrown.message : String(thrown)}. A tree whose access ` +
|
|
42
|
+
'cannot be read reports exactly like a tree that granted nothing, and a gate written from that would refuse ' +
|
|
43
|
+
'every caller rather than say so');
|
|
44
|
+
}
|
|
45
|
+
const list = held[name];
|
|
46
|
+
if (!Array.isArray(list))
|
|
47
|
+
stop(`${at} exports no \`${name}\` list, so nothing there states ${what}`);
|
|
48
|
+
return list;
|
|
49
|
+
}
|
|
50
|
+
const grants = await taken('access.ts', 'GRANTS', 'who may ask for what');
|
|
51
|
+
const operations = [...woven(pass, types(pass))].map(([id]) => ({
|
|
52
|
+
id,
|
|
53
|
+
describes: pass.stated.get(id)?.describes ?? '',
|
|
54
|
+
}));
|
|
55
|
+
for (const found of [unanswerable(grants)]) {
|
|
56
|
+
if (found.length > 0)
|
|
57
|
+
stop(found.join('; '));
|
|
58
|
+
}
|
|
59
|
+
const twice = grantedTwice(grants);
|
|
60
|
+
if (twice.length > 0) {
|
|
61
|
+
stop(`${twice.map((one) => `\`${one}\``).join(', ')} ${twice.length === 1 ? 'is granted' : 'are granted'} more than once, ` +
|
|
62
|
+
'so two arms answer to one id and either may permit. One id, one policy — rename one of them, or delete the ' +
|
|
63
|
+
'arm that is not meant to stand');
|
|
64
|
+
}
|
|
65
|
+
const open = opensToEverybody(grants);
|
|
66
|
+
if (open.length > 0) {
|
|
67
|
+
stop(`${open.map((one) => `\`${one.id}\``).join(', ')} names no roles or entitlements and states no condition that ` +
|
|
68
|
+
'reads the caller, so who may run this is left unsaid rather than decided. If it is meant to be open, say so: ' +
|
|
69
|
+
`\`to: { roles: ['${PUBLIC}'] }\` is the role an unauthenticated caller already wears, and naming it puts ` +
|
|
70
|
+
'that decision in the sheet. Otherwise name the roles that may, or state a `when` saying whose row it is');
|
|
71
|
+
}
|
|
72
|
+
const said = (held) => JSON.stringify(held);
|
|
73
|
+
function reads(one, grant) {
|
|
74
|
+
if ('value' in one)
|
|
75
|
+
return said(one.value);
|
|
76
|
+
if ('now' in one)
|
|
77
|
+
return 'Date.now()';
|
|
78
|
+
if ('ago' in one) {
|
|
79
|
+
const size = Number(one.ago.slice(0, -1));
|
|
80
|
+
const unit = one.ago.slice(-1);
|
|
81
|
+
const seconds = size * (unit === 's' ? 1 : unit === 'm' ? 60 : unit === 'h' ? 3_600 : 86_400);
|
|
82
|
+
return `(Date.now() - ${String(seconds * 1000)})`;
|
|
83
|
+
}
|
|
84
|
+
if ('actor' in one)
|
|
85
|
+
return `actor.${one.actor}`;
|
|
86
|
+
if ('field' in one) {
|
|
87
|
+
if (grant.on === undefined) {
|
|
88
|
+
stop(`\`${grant.id}\` reads the field "${one.field}" and names no entity in \`on\`, so there is nothing saying what ` +
|
|
89
|
+
'the row in front of the gate is. Name the entity the resource is, or state the rule without reading a field');
|
|
90
|
+
}
|
|
91
|
+
if (one.field.includes('.')) {
|
|
92
|
+
stop(`\`${grant.id}\` reads "${one.field}", which reaches through a link. The gate is handed one row and has ` +
|
|
93
|
+
'nothing to follow a link with — name a value of "' +
|
|
94
|
+
grant.on +
|
|
95
|
+
'" itself, or state the rule as an invariant where the rows are');
|
|
96
|
+
}
|
|
97
|
+
return `at(resource, ${said(one.field)})`;
|
|
98
|
+
}
|
|
99
|
+
stop(`\`${grant.id}\` states an operand this gate cannot read`);
|
|
100
|
+
}
|
|
101
|
+
function predicate(condition, grant) {
|
|
102
|
+
if ('all' in condition) {
|
|
103
|
+
return condition.all.length === 0 ? 'true' : `(${condition.all.map((one) => predicate(one, grant)).join(' && ')})`;
|
|
104
|
+
}
|
|
105
|
+
if ('any' in condition) {
|
|
106
|
+
return condition.any.length === 0 ? 'false' : `(${condition.any.map((one) => predicate(one, grant)).join(' || ')})`;
|
|
107
|
+
}
|
|
108
|
+
if ('not' in condition)
|
|
109
|
+
return `(!${predicate(condition.not, grant)})`;
|
|
110
|
+
return `held(${said(condition.op)}, ${reads(condition.is, grant)}, ${reads(condition.than, grant)})`;
|
|
111
|
+
}
|
|
112
|
+
function wears(grant) {
|
|
113
|
+
const held = [];
|
|
114
|
+
const roles = grant.to?.roles;
|
|
115
|
+
if (roles !== undefined && !roles.includes(PUBLIC))
|
|
116
|
+
held.push(`some(actor.roles, ${said(roles)})`);
|
|
117
|
+
if (grant.to?.entitlements !== undefined)
|
|
118
|
+
held.push(`some(actor.entitlements, ${said(grant.to.entitlements)})`);
|
|
119
|
+
return held;
|
|
120
|
+
}
|
|
121
|
+
const READS = ['const at = (row: Row | null, name: string): unknown => (row === null ? undefined : row[name])'];
|
|
122
|
+
const WEARS = [
|
|
123
|
+
'const some = (worn: readonly string[], named: readonly string[]): boolean => named.some((one) => worn.includes(one))',
|
|
124
|
+
];
|
|
125
|
+
const COMPARES = [
|
|
126
|
+
'const held = (op: string, left: unknown, right: unknown): boolean => {',
|
|
127
|
+
' if (op === \'in\') return Array.isArray(right) && right.includes(left)',
|
|
128
|
+
' if (op === \'has\') return Array.isArray(left) && left.includes(right)',
|
|
129
|
+
' if (op === \'eq\') return left === right',
|
|
130
|
+
' if (op === \'neq\') return left !== right',
|
|
131
|
+
' if (typeof left !== typeof right) return false',
|
|
132
|
+
' if (typeof left !== \'number\' && typeof left !== \'string\') return false',
|
|
133
|
+
' const other = right as number | string',
|
|
134
|
+
' if (op === \'lt\') return left < other',
|
|
135
|
+
' if (op === \'lte\') return left <= other',
|
|
136
|
+
' if (op === \'gt\') return left > other',
|
|
137
|
+
' return left >= other',
|
|
138
|
+
'}',
|
|
139
|
+
];
|
|
140
|
+
function permitting() {
|
|
141
|
+
const arms = grants.flatMap((one) => {
|
|
142
|
+
const parts = [...wears(one), ...(one.when === undefined ? [] : [predicate(one.when, one)])];
|
|
143
|
+
return [
|
|
144
|
+
` if (${said(one.may)}.includes(action)) {`,
|
|
145
|
+
` if (${parts.length === 0 ? 'true' : parts.join(' && ')}) return { allow: true, because: ${said(one.describes)} }`,
|
|
146
|
+
` denied.push(${said(one.describes)})`,
|
|
147
|
+
' }',
|
|
148
|
+
];
|
|
149
|
+
});
|
|
150
|
+
const body = grants.length === 0
|
|
151
|
+
? [
|
|
152
|
+
' return {',
|
|
153
|
+
' allow: false,',
|
|
154
|
+
" because: 'this app declares no grants, so nothing permits any operation. Declare one with `thread grant`',",
|
|
155
|
+
' }',
|
|
156
|
+
]
|
|
157
|
+
: [
|
|
158
|
+
' const denied: string[] = []',
|
|
159
|
+
...arms,
|
|
160
|
+
' return { allow: false, because: denied[0] ?? `nothing granted permits ${action}` }',
|
|
161
|
+
];
|
|
162
|
+
return [
|
|
163
|
+
`import type { Actor } from '${runtime(PERMITS_AT, '../../core/actor.ts')}'`,
|
|
164
|
+
`import type { Row } from '${runtime(PERMITS_AT, '../seams.ts')}'`,
|
|
165
|
+
'',
|
|
166
|
+
...(grants.some((one) => one.when !== undefined) ? [...READS, ''] : []),
|
|
167
|
+
...(grants.some((one) => one.to !== undefined) ? [...WEARS, ''] : []),
|
|
168
|
+
...(grants.some((one) => one.when !== undefined) ? [...COMPARES, ''] : []),
|
|
169
|
+
'export function permits(actor: Actor, action: string, resource: Row | null): { allow: boolean; because?: string } {',
|
|
170
|
+
...(grants.length === 0 ? [' void actor', ' void resource'] : []),
|
|
171
|
+
...body,
|
|
172
|
+
'}',
|
|
173
|
+
'',
|
|
174
|
+
].join('\n');
|
|
175
|
+
}
|
|
176
|
+
const RUNS = 'thread permits';
|
|
177
|
+
const permitted = resolve(ROOT, PERMITS_AT);
|
|
178
|
+
const content = permitting();
|
|
179
|
+
const count = `${String(grants.length)} grant(s) over ${String(operations.length)} operation(s)`;
|
|
180
|
+
if (CHECKING) {
|
|
181
|
+
const findings = [];
|
|
182
|
+
const current = existsSync(permitted) ? readFileSync(permitted, 'utf8') : '';
|
|
183
|
+
if (current !== content) {
|
|
184
|
+
findings.push({
|
|
185
|
+
file: PERMITS_AT,
|
|
186
|
+
detail: 'is not what this app grants',
|
|
187
|
+
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`,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
runChecker('permits', findings, count);
|
|
191
|
+
exit(findings.length === 0 ? 0 : 1);
|
|
192
|
+
}
|
|
193
|
+
mkdirSync(dirname(permitted), { recursive: true });
|
|
194
|
+
writeFileSync(permitted, content);
|
|
195
|
+
stdout.write(`permits: wrote ${PERMITS_AT} — ${count}. ${DECLARED_SEAMS_AT} hands it to dispatch as the gate every operation passes; ` +
|
|
196
|
+
'`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');
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type Pass, type Row } from '../../core/descend.ts';
|
|
2
|
+
import { quoted } from '../../core/rows.ts';
|
|
3
|
+
export { quoted };
|
|
4
|
+
export declare const beside: (from: string, to: string) => string;
|
|
5
|
+
export declare const runtime: (at: string, file: string) => string;
|
|
6
|
+
export declare function types(pass: Pass): ReadonlySet<string>;
|
|
7
|
+
export declare function builtFrom(row: Row, known: ReadonlySet<string>): string | undefined;
|
|
8
|
+
interface Woven {
|
|
9
|
+
readonly nodes: readonly Row[];
|
|
10
|
+
readonly takes: readonly Row[];
|
|
11
|
+
readonly body?: string;
|
|
12
|
+
readonly requires: readonly string[];
|
|
13
|
+
readonly stray: readonly string[];
|
|
14
|
+
}
|
|
15
|
+
export declare function operations(pass: Pass, known: ReadonlySet<string>): ReadonlyMap<string, Woven>;
|
|
16
|
+
export declare const problem: (id: string, one: Woven) => string | undefined;
|
|
17
|
+
export declare function blockIn(text: string): string | undefined;
|
|
18
|
+
export declare function scaffold(from: string, name: string): string;
|
|
19
|
+
export declare function nodeText(at: string, name: string, takes: string, from: string, requires: readonly string[], body?: string): string;
|
|
20
|
+
export declare function seed(bodies?: ReadonlyMap<string, string>, say?: boolean): number;
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, relative, resolve } from 'node:path';
|
|
3
|
+
import { argv, stdout } from 'node:process';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { ROOT, descend, under } from "../../core/descend.js";
|
|
6
|
+
import { runChecker } from "../../core/finding.js";
|
|
7
|
+
import { exit, invoked } from "../../core/invoked.js";
|
|
8
|
+
import { SUPPLIES, supplies } from "../seams.js";
|
|
9
|
+
const COMPILES = 'thread compile';
|
|
10
|
+
const OPERATIONS_ID = 'operations';
|
|
11
|
+
const NODES_ID = 'nodes';
|
|
12
|
+
const NAMED = /\btype:\s*'((?:[^'\\]|\\.)*)'/;
|
|
13
|
+
import { quoted } from "../../core/rows.js";
|
|
14
|
+
export { quoted };
|
|
15
|
+
const slash = (one) => one.split('\\').join('/');
|
|
16
|
+
export const beside = (from, to) => {
|
|
17
|
+
const said = slash(relative(dirname(from), to));
|
|
18
|
+
return said.startsWith('.') ? said : `./${said}`;
|
|
19
|
+
};
|
|
20
|
+
export const runtime = (at, file) => beside(resolve(ROOT, at), fileURLToPath(new URL(file, import.meta.url)));
|
|
21
|
+
const FRESH = {
|
|
22
|
+
nd_read: (throws) => `async () => {\n ${throws}\n}`,
|
|
23
|
+
nd_action: (throws) => `async () => {\n ${throws}\n}`,
|
|
24
|
+
nd_stop: (throws) => `async () => {\n ${throws}\n}`,
|
|
25
|
+
nd_each: (throws) => `() => [], async () => {\n ${throws}\n}`,
|
|
26
|
+
nd_repeat: (throws) => `1, async (pass, carried) => {\n ${throws}\n}`,
|
|
27
|
+
nd_parallel: (throws) => `[\n async () => {\n ${throws}\n },\n]`,
|
|
28
|
+
nd_branch: (throws) => `async () => 'one', {\n one: async () => {\n ${throws}\n },\n}`,
|
|
29
|
+
};
|
|
30
|
+
const SHIPPED = new Set(Object.keys(FRESH));
|
|
31
|
+
const isType = (pass, id) => under(pass, id, NODES_ID);
|
|
32
|
+
const isOurs = (pass, id) => under(pass, id, OPERATIONS_ID);
|
|
33
|
+
export function types(pass) {
|
|
34
|
+
const found = new Set(SHIPPED);
|
|
35
|
+
const named = new Set();
|
|
36
|
+
for (const threadings of pass.threaded.values()) {
|
|
37
|
+
for (const one of threadings)
|
|
38
|
+
if (one.row.id !== undefined)
|
|
39
|
+
named.add(one.row.id);
|
|
40
|
+
}
|
|
41
|
+
for (const one of pass.coined.keys()) {
|
|
42
|
+
if (one !== NODES_ID && !supplies(one) && !named.has(one) && under(pass, one, NODES_ID))
|
|
43
|
+
found.add(one);
|
|
44
|
+
}
|
|
45
|
+
return found;
|
|
46
|
+
}
|
|
47
|
+
export function builtFrom(row, known) {
|
|
48
|
+
const found = (row.requires ?? []).filter((one) => known.has(one));
|
|
49
|
+
return found.length === 1 ? found[0] : undefined;
|
|
50
|
+
}
|
|
51
|
+
const reaches = (pass, one) => pass.stated.has(one) && !under(pass, one, NODES_ID);
|
|
52
|
+
export function operations(pass, known) {
|
|
53
|
+
const found = new Map();
|
|
54
|
+
const held = (id) => {
|
|
55
|
+
const one = found.get(id);
|
|
56
|
+
if (one !== undefined)
|
|
57
|
+
return one;
|
|
58
|
+
const stated = pass.stated.get(id);
|
|
59
|
+
const asked = stated?.requires ?? [];
|
|
60
|
+
const made = {
|
|
61
|
+
nodes: [],
|
|
62
|
+
takes: [],
|
|
63
|
+
body: undefined,
|
|
64
|
+
wants: new Set(stated?.takes ?? []),
|
|
65
|
+
requires: asked.filter((one) => supplies(one) || reaches(pass, one)),
|
|
66
|
+
stray: asked.filter((one) => !supplies(one) && !reaches(pass, one)),
|
|
67
|
+
};
|
|
68
|
+
found.set(id, made);
|
|
69
|
+
return made;
|
|
70
|
+
};
|
|
71
|
+
for (const [at, threadings] of pass.threaded) {
|
|
72
|
+
for (const one of threadings) {
|
|
73
|
+
if (one.by === OPERATIONS_ID || !isOurs(pass, one.by) || isType(pass, one.by))
|
|
74
|
+
continue;
|
|
75
|
+
if (one.list === 'derives' && one.row.by === COMPILES)
|
|
76
|
+
held(one.by).body = at;
|
|
77
|
+
else if (one.list === 'seeds') {
|
|
78
|
+
const mine = held(one.by);
|
|
79
|
+
if (mine.wants.has(one.row.id ?? ''))
|
|
80
|
+
mine.takes.push(one.row);
|
|
81
|
+
else
|
|
82
|
+
mine.nodes.push(one.row);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return found;
|
|
87
|
+
}
|
|
88
|
+
export const problem = (id, one) => {
|
|
89
|
+
if (one.nodes.length === 0)
|
|
90
|
+
return `\`${id}\` is an operation that seeds no nodes, and an operation is its nodes`;
|
|
91
|
+
if (one.takes.length === 0) {
|
|
92
|
+
return (`\`${id}\` names nothing it takes, and an operation takes one thing — its input, its estimate and the row it touches. ` +
|
|
93
|
+
`Write \`takes: ['${id}_takes']\` on its row beside the file that row seeds`);
|
|
94
|
+
}
|
|
95
|
+
if (one.takes.length > 1)
|
|
96
|
+
return `\`${id}\` takes ${String(one.takes.length)} things, and it takes one`;
|
|
97
|
+
if (one.stray.length > 0) {
|
|
98
|
+
return (`\`${id}\` requires ${one.stray.join(', ')}, and dispatch can supply neither a capability nor a target by that ` +
|
|
99
|
+
`name. What an operation may require is ${SUPPLIES.join(', ')}, or a target this app declares — ` +
|
|
100
|
+
'declare one with `thread create target`');
|
|
101
|
+
}
|
|
102
|
+
return undefined;
|
|
103
|
+
};
|
|
104
|
+
const takesFile = (at) => [
|
|
105
|
+
"import { z } from 'zod'",
|
|
106
|
+
'',
|
|
107
|
+
`import type { Row } from ${quoted(runtime(at, '../seams.ts'))}`,
|
|
108
|
+
'',
|
|
109
|
+
'export const input = z.unknown()',
|
|
110
|
+
'',
|
|
111
|
+
'export const estimate = 1',
|
|
112
|
+
'',
|
|
113
|
+
'export const resource = (): Row | null => null',
|
|
114
|
+
'',
|
|
115
|
+
'export type In = z.infer<typeof input>',
|
|
116
|
+
'',
|
|
117
|
+
].join('\n');
|
|
118
|
+
const OPENS = 'export const node = ';
|
|
119
|
+
export function blockIn(text) {
|
|
120
|
+
const at = text.indexOf(OPENS);
|
|
121
|
+
if (at === -1)
|
|
122
|
+
return undefined;
|
|
123
|
+
const open = text.indexOf('(', at);
|
|
124
|
+
const shut = text.lastIndexOf(')');
|
|
125
|
+
if (open === -1 || shut <= open)
|
|
126
|
+
return undefined;
|
|
127
|
+
return text.slice(open + 1, shut);
|
|
128
|
+
}
|
|
129
|
+
export function scaffold(from, name) {
|
|
130
|
+
const throws = `throw new Error(${quoted(`the node ${name} was scaffolded and never written`)})`;
|
|
131
|
+
return (FRESH[from] ?? ((one) => `async () => {\n ${one}\n}`))(throws);
|
|
132
|
+
}
|
|
133
|
+
export function nodeText(at, name, takes, from, requires, body) {
|
|
134
|
+
const held = SHIPPED.has(from)
|
|
135
|
+
? `import { ${from} } from ${quoted(runtime(at, '../nodes.ts'))}`
|
|
136
|
+
: `import { node as ${from} } from ${quoted(beside(at, `nodes/${from}.ts`))}`;
|
|
137
|
+
const only = requires.length === 0 ? 'never' : requires.map((one) => quoted(one)).join(' | ');
|
|
138
|
+
return [
|
|
139
|
+
held,
|
|
140
|
+
'',
|
|
141
|
+
`import type { In } from ${quoted(beside(at, takes))}`,
|
|
142
|
+
'',
|
|
143
|
+
`${OPENS}${from}<In, ${only}>(${body ?? scaffold(from, name)})`,
|
|
144
|
+
'',
|
|
145
|
+
].join('\n');
|
|
146
|
+
}
|
|
147
|
+
const nodeFile = (at, row, takes, from, requires, body) => nodeText(at, row.id ?? '', takes, from, requires, body);
|
|
148
|
+
const ownType = (at, id) => [
|
|
149
|
+
`import type { Held } from ${quoted(runtime(at, '../nodes.ts'))}`,
|
|
150
|
+
`import type { Handed, Supplied } from ${quoted(runtime(at, '../seams.ts'))}`,
|
|
151
|
+
'',
|
|
152
|
+
'export const node = <I, R extends Supplied = Supplied>(block: (hands: Handed<R>, held: Held<I>) => Promise<unknown>) => ({',
|
|
153
|
+
` type: ${quoted(id)} as const,`,
|
|
154
|
+
'})',
|
|
155
|
+
'',
|
|
156
|
+
].join('\n');
|
|
157
|
+
function planned(bodies) {
|
|
158
|
+
const pass = descend();
|
|
159
|
+
const known = types(pass);
|
|
160
|
+
const mine = operations(pass, known);
|
|
161
|
+
const found = [];
|
|
162
|
+
for (const [id, one] of mine) {
|
|
163
|
+
void id;
|
|
164
|
+
for (const row of one.takes)
|
|
165
|
+
if (row.at !== undefined)
|
|
166
|
+
found.push({ at: row.at, derived: false, text: takesFile(row.at) });
|
|
167
|
+
for (const row of one.nodes) {
|
|
168
|
+
const from = builtFrom(row, known);
|
|
169
|
+
const takes = one.takes[0]?.at;
|
|
170
|
+
if (row.at === undefined || from === undefined || takes === undefined)
|
|
171
|
+
continue;
|
|
172
|
+
const path = resolve(ROOT, row.at);
|
|
173
|
+
const held = existsSync(path) ? readFileSync(path, 'utf8') : undefined;
|
|
174
|
+
const block = bodies.get(row.at) ?? (held === undefined ? undefined : blockIn(held));
|
|
175
|
+
if (held !== undefined && block === undefined) {
|
|
176
|
+
found.push({
|
|
177
|
+
at: row.at,
|
|
178
|
+
derived: true,
|
|
179
|
+
because: `\`${String(row.id)}\` states no \`${OPENS}\` this can read, so the block it holds cannot be told from the ` +
|
|
180
|
+
'wrapper its rows own. Delete the file and run `thread seed` to scaffold it again, then write the block back into it',
|
|
181
|
+
});
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
found.push({ at: row.at, derived: true, wraps: String(row.id), text: nodeFile(row.at, row, takes, from, one.requires, block) });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
for (const [at, threadings] of pass.threaded) {
|
|
188
|
+
for (const one of threadings) {
|
|
189
|
+
if (one.list !== 'seeds' || !isType(pass, one.by) || SHIPPED.has(one.by))
|
|
190
|
+
continue;
|
|
191
|
+
found.push({ at, derived: false, text: ownType(at, one.by), type: one.by });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return found;
|
|
195
|
+
}
|
|
196
|
+
function checked(plan) {
|
|
197
|
+
const findings = plan.flatMap((one) => {
|
|
198
|
+
const path = resolve(ROOT, one.at);
|
|
199
|
+
if (one.because !== undefined) {
|
|
200
|
+
return [
|
|
201
|
+
{
|
|
202
|
+
file: one.at,
|
|
203
|
+
detail: one.because,
|
|
204
|
+
instead: 'declare it properly — this file is written from those rows and cannot be written without them',
|
|
205
|
+
},
|
|
206
|
+
];
|
|
207
|
+
}
|
|
208
|
+
if (!existsSync(path)) {
|
|
209
|
+
return [
|
|
210
|
+
{
|
|
211
|
+
file: one.at,
|
|
212
|
+
detail: 'a row declares this file and nothing is there',
|
|
213
|
+
instead: 'run `thread seed` and write what it leaves — a row that seeded nothing is half a declaration',
|
|
214
|
+
},
|
|
215
|
+
];
|
|
216
|
+
}
|
|
217
|
+
if (one.type !== undefined) {
|
|
218
|
+
const said = NAMED.exec(readFileSync(path, 'utf8'))?.[1];
|
|
219
|
+
if (said === one.type)
|
|
220
|
+
return [];
|
|
221
|
+
return [
|
|
222
|
+
{
|
|
223
|
+
file: one.at,
|
|
224
|
+
detail: said === undefined
|
|
225
|
+
? `\`${one.type}\` seeds this type and the file states no name of its own`
|
|
226
|
+
: `\`${one.type}\` seeds this type and the file calls itself \`${said}\``,
|
|
227
|
+
instead: "state the row's own id as the type's name. A node declares which type it is built from, and the compiler holds the two together by that name",
|
|
228
|
+
},
|
|
229
|
+
];
|
|
230
|
+
}
|
|
231
|
+
if (!one.derived)
|
|
232
|
+
return [];
|
|
233
|
+
if (readFileSync(path, 'utf8') === one.text)
|
|
234
|
+
return [];
|
|
235
|
+
if (one.wraps !== undefined) {
|
|
236
|
+
return [
|
|
237
|
+
{
|
|
238
|
+
file: one.at,
|
|
239
|
+
detail: `the line that opens \`${one.wraps}\` is written from its rows, and this is no longer the bytes they write`,
|
|
240
|
+
instead: 'run `thread seed`. Which type builds this node and what its operation lets it touch are the rows to say, not this line — the block inside the call is yours and is put back exactly as you left it',
|
|
241
|
+
},
|
|
242
|
+
];
|
|
243
|
+
}
|
|
244
|
+
return [
|
|
245
|
+
{
|
|
246
|
+
file: one.at,
|
|
247
|
+
detail: 'this operation derives its body, and this is no longer the bytes that derive it',
|
|
248
|
+
instead: 'run `thread seed`, or put back what it writes. A derived body has no author: to change what an operation does, change the rows that declare its nodes, and to change what a node does, write it in the file that node seeds',
|
|
249
|
+
},
|
|
250
|
+
];
|
|
251
|
+
});
|
|
252
|
+
runChecker('seed', findings, `${String(plan.length)} file(s) declared`);
|
|
253
|
+
exit(findings.length === 0 ? 0 : 1);
|
|
254
|
+
}
|
|
255
|
+
export function seed(bodies = new Map(), say = true) {
|
|
256
|
+
const plan = planned(bodies);
|
|
257
|
+
let written = 0;
|
|
258
|
+
for (const one of plan) {
|
|
259
|
+
const path = resolve(ROOT, one.at);
|
|
260
|
+
const held = existsSync(path) ? readFileSync(path, 'utf8') : undefined;
|
|
261
|
+
if (held !== undefined && !one.derived && !bodies.has(one.at))
|
|
262
|
+
continue;
|
|
263
|
+
if (one.text === undefined) {
|
|
264
|
+
if (say)
|
|
265
|
+
stdout.write(`seed: ${one.at} was not written — ${one.because ?? 'the rows behind it are incomplete'}\n`);
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (held === one.text)
|
|
269
|
+
continue;
|
|
270
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
271
|
+
writeFileSync(path, one.text, 'utf8');
|
|
272
|
+
written += 1;
|
|
273
|
+
if (say)
|
|
274
|
+
stdout.write(`seed: wrote ${one.at}\n`);
|
|
275
|
+
}
|
|
276
|
+
if (say)
|
|
277
|
+
stdout.write(`seed: ${String(written)} of ${String(plan.length)} file(s) written, ${String(plan.length - written)} already as declared.\n`);
|
|
278
|
+
return written;
|
|
279
|
+
}
|
|
280
|
+
if (invoked(import.meta.url)) {
|
|
281
|
+
if (argv.includes('--check'))
|
|
282
|
+
checked(planned(new Map()));
|
|
283
|
+
else
|
|
284
|
+
seed();
|
|
285
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type Actor } from '../../core/actor.ts';
|
|
2
|
+
import { type ScriptedAnswer } from '../http.ts';
|
|
3
|
+
import { TRIALS_AT, type Operation } from '../operations.ts';
|
|
4
|
+
import type { Result } from '../result.ts';
|
|
5
|
+
import type { Row, Seams } from '../seams.ts';
|
|
6
|
+
export { TRIALS_AT };
|
|
7
|
+
export interface Scenario {
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly describes: string;
|
|
10
|
+
readonly input: unknown;
|
|
11
|
+
readonly actor?: Actor;
|
|
12
|
+
readonly rows?: readonly {
|
|
13
|
+
readonly entity: string;
|
|
14
|
+
readonly row: Row;
|
|
15
|
+
}[];
|
|
16
|
+
readonly answers?: Readonly<Record<string, ScriptedAnswer>>;
|
|
17
|
+
readonly views?: Readonly<Record<string, readonly unknown[]>>;
|
|
18
|
+
}
|
|
19
|
+
export interface Trial {
|
|
20
|
+
readonly operation: string;
|
|
21
|
+
readonly scenario: string;
|
|
22
|
+
readonly refused: boolean;
|
|
23
|
+
readonly measured: boolean;
|
|
24
|
+
readonly result: unknown;
|
|
25
|
+
readonly kept: unknown;
|
|
26
|
+
readonly observed: unknown;
|
|
27
|
+
readonly called: unknown;
|
|
28
|
+
}
|
|
29
|
+
export interface Outcome {
|
|
30
|
+
readonly id: string;
|
|
31
|
+
readonly operation: string;
|
|
32
|
+
readonly passed: boolean;
|
|
33
|
+
readonly refused: boolean;
|
|
34
|
+
readonly measured: boolean;
|
|
35
|
+
readonly failures: readonly string[];
|
|
36
|
+
}
|
|
37
|
+
export interface Run {
|
|
38
|
+
readonly outcomes: readonly Outcome[];
|
|
39
|
+
readonly passed: number;
|
|
40
|
+
readonly failed: number;
|
|
41
|
+
readonly measured: number;
|
|
42
|
+
}
|
|
43
|
+
export declare function runScenario(one: Operation<unknown>, scenario: Scenario, dispatch: (operation: Operation<unknown>, input: unknown, actor: Actor, seams: Seams) => Promise<Result<unknown>>, stood?: Readonly<Record<string, string>>, gate?: NonNullable<Seams['permits']>, known?: {
|
|
44
|
+
readonly entities: ReadonlyMap<string, ReadonlySet<string>>;
|
|
45
|
+
readonly keys: ReadonlyMap<string, string>;
|
|
46
|
+
}): Promise<Trial>;
|
|
47
|
+
export declare function scenariosFor(one: Operation<unknown>, takes: string, granted?: readonly string[]): Promise<readonly Scenario[] | string>;
|
|
48
|
+
export declare function compared(was: readonly Trial[], now: readonly Trial[]): Run;
|
|
49
|
+
export declare function unproven(run: Run): readonly string[];
|
|
50
|
+
export declare function record(): Promise<{
|
|
51
|
+
readonly trials: readonly Trial[];
|
|
52
|
+
readonly refusals: readonly string[];
|
|
53
|
+
}>;
|