@codyswann/lisa 3.62.0 → 3.64.0
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/all/copy-overwrite/scripts/lisa-gates.mjs +223 -10
- package/all/copy-overwrite/scripts/lisa-reconcile-policy.mjs +325 -32
- package/dist/cli/gate-report-templates.js +1 -1
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +8 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +16 -9
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/health/ruleset-inspection.d.ts.map +1 -1
- package/dist/health/ruleset-inspection.js +73 -6
- package/dist/health/ruleset-inspection.js.map +1 -1
- package/package.json +2 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-doctor/SKILL.md +46 -7
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-github-repo/SKILL.md +7 -3
- package/plugins/lisa/skills/lisa-doctor/SKILL.md +46 -7
- package/plugins/lisa/skills/lisa-setup-github-repo/SKILL.md +7 -3
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-doctor/SKILL.md +46 -7
- package/plugins/lisa-agy/skills/lisa-setup-github-repo/SKILL.md +7 -3
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/skills/lisa-doctor/SKILL.md +46 -7
- package/plugins/lisa-copilot/skills/lisa-setup-github-repo/SKILL.md +7 -3
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/skills/lisa-doctor/SKILL.md +46 -7
- package/plugins/lisa-cursor/skills/lisa-setup-github-repo/SKILL.md +7 -3
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/skills/lisa-doctor/SKILL.md +46 -7
- package/plugins/src/base/skills/lisa-setup-github-repo/SKILL.md +7 -3
- package/scripts/check-required-check-promotions.mjs +69 -4
- package/scripts/lisa-github-repo-setup.sh +64 -5
- package/scripts/lisa-github-rulesets.sh +201 -21
- package/scripts/lisa-ruleset-payload.mjs +376 -0
- package/all/github-rulesets/base.json +0 -64
|
@@ -1974,12 +1974,32 @@ export const EVIDENCE_DEFAULTS = Object.freeze({
|
|
|
1974
1974
|
]),
|
|
1975
1975
|
});
|
|
1976
1976
|
|
|
1977
|
+
/**
|
|
1978
|
+
* How hard a ruleset bites.
|
|
1979
|
+
*
|
|
1980
|
+
* `evaluate` and `disabled` are not milder versions of `active` — a ruleset in
|
|
1981
|
+
* either state asserts NOTHING, which is why `rulesetSignals` refuses to read
|
|
1982
|
+
* policy off one. Declaring the value is what lets a project say it is running
|
|
1983
|
+
* a ruleset in dry-run on purpose instead of having it silently do nothing.
|
|
1984
|
+
*/
|
|
1985
|
+
export const ENFORCEMENTS = Object.freeze(["active", "evaluate", "disabled"]);
|
|
1986
|
+
|
|
1977
1987
|
/**
|
|
1978
1988
|
* Repository policy Lisa asserts.
|
|
1979
1989
|
*
|
|
1980
1990
|
* Policy differs from a gate in what failure means. A gate failing says stop
|
|
1981
1991
|
* the change; a policy having drifted says put the setting back. That is why
|
|
1982
1992
|
* `on_drift` defaults to repair and a gate never does.
|
|
1993
|
+
*
|
|
1994
|
+
* `review` and `ruleset` describe the SHAPE of the branch ruleset Lisa builds,
|
|
1995
|
+
* and they exist because a shipped `all/github-rulesets/base.json` used to
|
|
1996
|
+
* carry them instead. Seven of its fields were already declared here, so two
|
|
1997
|
+
* writers set the same settings and whichever ran last won with no drift
|
|
1998
|
+
* report between them; four more — `bypass_actors`, the `ref_name` conditions,
|
|
1999
|
+
* `required_approving_review_count`, `enforcement` — could not be declared at
|
|
2000
|
+
* all, which made a template value a fleet-wide lock no project could override.
|
|
2001
|
+
* The template is gone and the applier generates its payload from these fields,
|
|
2002
|
+
* so there is one writer and one declaration.
|
|
1983
2003
|
*/
|
|
1984
2004
|
export const POLICY_SCHEMA = Object.freeze({
|
|
1985
2005
|
merge: Object.freeze({
|
|
@@ -2008,6 +2028,16 @@ export const POLICY_SCHEMA = Object.freeze({
|
|
|
2008
2028
|
has_wiki: "boolean",
|
|
2009
2029
|
default_branch: "string",
|
|
2010
2030
|
}),
|
|
2031
|
+
review: Object.freeze({
|
|
2032
|
+
required_approving_review_count: "number",
|
|
2033
|
+
require_code_owner_review: "boolean",
|
|
2034
|
+
}),
|
|
2035
|
+
ruleset: Object.freeze({
|
|
2036
|
+
enforcement: ENFORCEMENTS,
|
|
2037
|
+
include_refs: "string[]",
|
|
2038
|
+
exclude_refs: "string[]",
|
|
2039
|
+
bypass_actors: "object[]",
|
|
2040
|
+
}),
|
|
2011
2041
|
});
|
|
2012
2042
|
|
|
2013
2043
|
/** How to respond when reality has drifted from declared policy. */
|
|
@@ -2059,6 +2089,31 @@ export const RETIRED_CONFIG_KEYS = Object.freeze({
|
|
|
2059
2089
|
*/
|
|
2060
2090
|
export const DEFAULT_RUNNER = "npm run";
|
|
2061
2091
|
|
|
2092
|
+
/**
|
|
2093
|
+
* What a presence-gated CI job does when nothing proves its property.
|
|
2094
|
+
*
|
|
2095
|
+
* Six `quality.yml` jobs decide what to run by grepping for a script. When the
|
|
2096
|
+
* script is absent AND no declaration covers the gate, they used to print a
|
|
2097
|
+
* notice and exit 0 — and two of the six post required branch-protection
|
|
2098
|
+
* contexts, so the context reported satisfied having proved nothing. That is
|
|
2099
|
+
* strictly worse than having no gate, because it manufactures evidence.
|
|
2100
|
+
*
|
|
2101
|
+
* This is ONE control for the whole family, deliberately. `bdd_coverage`
|
|
2102
|
+
* hand-rolled a private three-state input (`bdd_mode`) for the same decision;
|
|
2103
|
+
* repeating that six more times would be six more adoption operations and six
|
|
2104
|
+
* more places to drift.
|
|
2105
|
+
*
|
|
2106
|
+
* `warn` is the default and reproduces today's behaviour exactly, because
|
|
2107
|
+
* making absence fatal before a declaration is guaranteed turns every
|
|
2108
|
+
* unconfigured consumer red on the next bump — the ordering #2838 states.
|
|
2109
|
+
*/
|
|
2110
|
+
export const UNPROVEN_RESPONSES = Object.freeze(["warn", "fail"]);
|
|
2111
|
+
|
|
2112
|
+
/**
|
|
2113
|
+
* The response Lisa assumes when a project declares none.
|
|
2114
|
+
*/
|
|
2115
|
+
export const DEFAULT_UNPROVEN = "warn";
|
|
2116
|
+
|
|
2062
2117
|
/**
|
|
2063
2118
|
* Commands that ignore their arguments and return a fixed status.
|
|
2064
2119
|
*
|
|
@@ -2107,14 +2162,25 @@ export function isRunner(value) {
|
|
|
2107
2162
|
* The previous destructuring default fired only on `undefined`, so `null`,
|
|
2108
2163
|
* `true` and `0` all became the runner, and `list --json` emitted whatever was
|
|
2109
2164
|
* in the file for nineteen workflow facades to consume.
|
|
2165
|
+
* `unproven` is split out for the same reason: it is a RESERVED sibling of
|
|
2166
|
+
* `runner`, not a gate id. Left in the block it reaches `validateGates` as
|
|
2167
|
+
* `gates."unproven" is not a gate Lisa knows` — a BLOCKING problem — so a
|
|
2168
|
+
* project that set the control would have `validate` refuse it and get no
|
|
2169
|
+
* enforcement either.
|
|
2110
2170
|
* @param {string} [cwd] Directory to look in.
|
|
2111
|
-
* @returns {{runner: string, gates: object, policy: object, config: object}} Parsed config.
|
|
2112
|
-
* @throws {Error} When `gates.runner` is
|
|
2171
|
+
* @returns {{runner: string, unproven: string, gates: object, policy: object, config: object}} Parsed config.
|
|
2172
|
+
* @throws {Error} When `gates.runner` is not a runner, or `gates.unproven` is not a permitted response.
|
|
2113
2173
|
*/
|
|
2114
2174
|
export function readGates(cwd = process.cwd()) {
|
|
2115
2175
|
const path = join(cwd, ".lisa.config.json");
|
|
2116
2176
|
if (!existsSync(path)) {
|
|
2117
|
-
return {
|
|
2177
|
+
return {
|
|
2178
|
+
runner: DEFAULT_RUNNER,
|
|
2179
|
+
unproven: DEFAULT_UNPROVEN,
|
|
2180
|
+
gates: {},
|
|
2181
|
+
policy: {},
|
|
2182
|
+
config: {},
|
|
2183
|
+
};
|
|
2118
2184
|
}
|
|
2119
2185
|
let config;
|
|
2120
2186
|
try {
|
|
@@ -2122,7 +2188,22 @@ export function readGates(cwd = process.cwd()) {
|
|
|
2122
2188
|
} catch (err) {
|
|
2123
2189
|
throw new Error(`.lisa.config.json is not readable: ${err.message}`);
|
|
2124
2190
|
}
|
|
2125
|
-
const {
|
|
2191
|
+
const {
|
|
2192
|
+
runner: declared,
|
|
2193
|
+
unproven: declaredUnproven,
|
|
2194
|
+
...gates
|
|
2195
|
+
} = config.gates ?? {};
|
|
2196
|
+
const unproven =
|
|
2197
|
+
declaredUnproven === undefined ? DEFAULT_UNPROVEN : declaredUnproven;
|
|
2198
|
+
if (!UNPROVEN_RESPONSES.includes(unproven)) {
|
|
2199
|
+
throw new Error(
|
|
2200
|
+
`gates.unproven is ${JSON.stringify(declaredUnproven)}, which is not ` +
|
|
2201
|
+
`one of ${UNPROVEN_RESPONSES.join(", ")}. An unrecognised response is ` +
|
|
2202
|
+
`refused rather than defaulted, because falling through to the ` +
|
|
2203
|
+
`permissive value is how a typo silently turns enforcement off for ` +
|
|
2204
|
+
`the project that asked for it.`
|
|
2205
|
+
);
|
|
2206
|
+
}
|
|
2126
2207
|
const runner = declared === undefined ? DEFAULT_RUNNER : declared;
|
|
2127
2208
|
if (!isRunner(runner)) {
|
|
2128
2209
|
throw new Error(
|
|
@@ -2133,7 +2214,7 @@ export function readGates(cwd = process.cwd()) {
|
|
|
2133
2214
|
`anything, which turns every configured gate green while proving nothing.`
|
|
2134
2215
|
);
|
|
2135
2216
|
}
|
|
2136
|
-
return { runner, gates, policy: config.policy ?? {}, config };
|
|
2217
|
+
return { runner, unproven, gates, policy: config.policy ?? {}, config };
|
|
2137
2218
|
}
|
|
2138
2219
|
|
|
2139
2220
|
/**
|
|
@@ -2247,6 +2328,54 @@ export function validateGates(gates) {
|
|
|
2247
2328
|
for (const [id, gate] of Object.entries(gates ?? {})) {
|
|
2248
2329
|
problems.push(...validateGate(id, gate));
|
|
2249
2330
|
}
|
|
2331
|
+
problems.push(...validateAwaitedContexts(gates));
|
|
2332
|
+
return problems;
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
/**
|
|
2336
|
+
* Refuse two required gates that await one context with different pins.
|
|
2337
|
+
*
|
|
2338
|
+
* Two gates may legitimately be proved by the same external signal, and the
|
|
2339
|
+
* ruleset carries one entry per context — so the payload writer has to collapse
|
|
2340
|
+
* them. Collapsing silently keeps whichever it met first and DISCARDS the other
|
|
2341
|
+
* declaration's `posted_by`, which is how a project ends up requiring a context
|
|
2342
|
+
* pinned to an app it never named. An omitted id is not "no opinion" either: it
|
|
2343
|
+
* means unpinned, GitHub's "any source", which is a different requirement from
|
|
2344
|
+
* a pinned one.
|
|
2345
|
+
*
|
|
2346
|
+
* So an exact duplicate — same context, same pin, including both unpinned — is
|
|
2347
|
+
* fine and collapses. Anything else is refused here, before a payload is built
|
|
2348
|
+
* from it.
|
|
2349
|
+
* @param {object} gates The gates block.
|
|
2350
|
+
* @returns {string[]} Problems.
|
|
2351
|
+
*/
|
|
2352
|
+
function validateAwaitedContexts(gates) {
|
|
2353
|
+
const problems = [];
|
|
2354
|
+
const seen = new Map();
|
|
2355
|
+
for (const [id, gate] of Object.entries(gates ?? {})) {
|
|
2356
|
+
if (!gate || typeof gate !== "object" || Array.isArray(gate)) continue;
|
|
2357
|
+
for (const [moment, value] of Object.entries(gate)) {
|
|
2358
|
+
const entry =
|
|
2359
|
+
typeof value === "string" ? { level: value } : (value ?? {});
|
|
2360
|
+
if (entry.level !== "required" || !entry.await) continue;
|
|
2361
|
+
const key = `${moment}\u0000${entry.await}`;
|
|
2362
|
+
const pin = entry.posted_by ?? null;
|
|
2363
|
+
const previous = seen.get(key);
|
|
2364
|
+
if (previous === undefined) {
|
|
2365
|
+
seen.set(key, { id, pin });
|
|
2366
|
+
continue;
|
|
2367
|
+
}
|
|
2368
|
+
if (previous.pin === pin) continue;
|
|
2369
|
+
problems.push(
|
|
2370
|
+
`gates."${id}"."${moment}" and gates."${previous.id}"."${moment}" both ` +
|
|
2371
|
+
`await "${entry.await}" but name different apps ` +
|
|
2372
|
+
`(${JSON.stringify(pin)} vs ${JSON.stringify(previous.pin)}). A ruleset ` +
|
|
2373
|
+
`carries ONE entry per context, so one of these pins would be dropped ` +
|
|
2374
|
+
`without a word — and an omitted posted_by means unpinned, which is a ` +
|
|
2375
|
+
`different requirement from a pinned one, not an absent opinion.`
|
|
2376
|
+
);
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2250
2379
|
return problems;
|
|
2251
2380
|
}
|
|
2252
2381
|
|
|
@@ -2397,6 +2526,7 @@ function validateMoment(id, moment, value, known, interceptor, gateRun) {
|
|
|
2397
2526
|
}
|
|
2398
2527
|
problems.push(...validateEvidence(id, moment, entry.evidence ?? {}));
|
|
2399
2528
|
}
|
|
2529
|
+
problems.push(...validatePostedBy(id, moment, entry));
|
|
2400
2530
|
if (!known && !interceptor && !entry.await && !entry.run && !gateRun) {
|
|
2401
2531
|
problems.push(
|
|
2402
2532
|
`gates."${id}"."${moment}" names no prover and Lisa has no default task ` +
|
|
@@ -2406,6 +2536,45 @@ function validateMoment(id, moment, value, known, interceptor, gateRun) {
|
|
|
2406
2536
|
return problems;
|
|
2407
2537
|
}
|
|
2408
2538
|
|
|
2539
|
+
/**
|
|
2540
|
+
* Validate the app pin on an awaited signal.
|
|
2541
|
+
*
|
|
2542
|
+
* A required status check can name the ONE app allowed to post it, which is
|
|
2543
|
+
* what stops any other writer satisfying it. The pin therefore has to travel
|
|
2544
|
+
* with the declaration of who posts the signal, not with the ruleset payload —
|
|
2545
|
+
* a shipped template that hardcoded two vendor integration ids is precisely
|
|
2546
|
+
* what this replaces, and it was a fleet-wide lock no project could override.
|
|
2547
|
+
*
|
|
2548
|
+
* Refused on a gate Lisa RUNS, because those are posted by GitHub Actions and
|
|
2549
|
+
* the applier pins them to it already; a second, different pin on the same
|
|
2550
|
+
* context would name an app that can never post it and block every pull
|
|
2551
|
+
* request in the repository forever.
|
|
2552
|
+
* @param {string} id Gate id.
|
|
2553
|
+
* @param {string} moment Moment key.
|
|
2554
|
+
* @param {object} entry The moment entry.
|
|
2555
|
+
* @returns {string[]} Problems.
|
|
2556
|
+
*/
|
|
2557
|
+
function validatePostedBy(id, moment, entry) {
|
|
2558
|
+
if (entry.posted_by === undefined) return [];
|
|
2559
|
+
const where = `gates."${id}"."${moment}".posted_by`;
|
|
2560
|
+
if (!entry.await) {
|
|
2561
|
+
return [
|
|
2562
|
+
`${where} names the app that posts a signal, but this moment declares ` +
|
|
2563
|
+
`no await. Lisa posts its own gates through GitHub Actions and pins ` +
|
|
2564
|
+
`them itself; a second pin would name an app that never posts the ` +
|
|
2565
|
+
`context, and a required check nothing can post blocks every pull ` +
|
|
2566
|
+
`request.`,
|
|
2567
|
+
];
|
|
2568
|
+
}
|
|
2569
|
+
if (!Number.isInteger(entry.posted_by) || entry.posted_by <= 0) {
|
|
2570
|
+
return [
|
|
2571
|
+
`${where} is ${JSON.stringify(entry.posted_by)}; expected the positive ` +
|
|
2572
|
+
`integer GitHub App id that posts "${entry.await}".`,
|
|
2573
|
+
];
|
|
2574
|
+
}
|
|
2575
|
+
return [];
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2409
2578
|
/**
|
|
2410
2579
|
* Validate an awaited signal's evidence block.
|
|
2411
2580
|
* @param {string} id Gate id.
|
|
@@ -2480,6 +2649,49 @@ function validateNeeds(id, gate) {
|
|
|
2480
2649
|
return problems;
|
|
2481
2650
|
}
|
|
2482
2651
|
|
|
2652
|
+
/**
|
|
2653
|
+
* Describe why a declared policy value does not fit its declared type.
|
|
2654
|
+
*
|
|
2655
|
+
* The vocabulary grew past `typeof` when the ruleset shape moved into config:
|
|
2656
|
+
* `bypass_actors` is an array of objects and `include_refs` an array of
|
|
2657
|
+
* strings, and `typeof` calls both of them "object", so the original check
|
|
2658
|
+
* would have accepted `"include_refs": {}` — a condition list that silently
|
|
2659
|
+
* matches no branch, which is a ruleset that protects nothing while reading as
|
|
2660
|
+
* configured. An expected value given as an ARRAY is a closed set of literals.
|
|
2661
|
+
* @param {string|readonly string[]} expected The schema entry.
|
|
2662
|
+
* @param {*} value The declared value.
|
|
2663
|
+
* @returns {string|null} The problem, or null when the value fits.
|
|
2664
|
+
*/
|
|
2665
|
+
function policyTypeProblem(expected, value) {
|
|
2666
|
+
if (Array.isArray(expected)) {
|
|
2667
|
+
return expected.includes(value)
|
|
2668
|
+
? null
|
|
2669
|
+
: `must be one of ${expected.join(", ")}, got ${JSON.stringify(value)}`;
|
|
2670
|
+
}
|
|
2671
|
+
if (expected === "string[]" || expected === "object[]") {
|
|
2672
|
+
const member = expected === "string[]" ? "string" : "object";
|
|
2673
|
+
if (!Array.isArray(value)) {
|
|
2674
|
+
return `must be an array of ${member}s, got ${typeof value}`;
|
|
2675
|
+
}
|
|
2676
|
+
const bad = value.findIndex(entry =>
|
|
2677
|
+
member === "string"
|
|
2678
|
+
? typeof entry !== "string"
|
|
2679
|
+
: !entry || typeof entry !== "object" || Array.isArray(entry)
|
|
2680
|
+
);
|
|
2681
|
+
return bad === -1
|
|
2682
|
+
? null
|
|
2683
|
+
: `must be an array of ${member}s; entry ${bad} is ${JSON.stringify(value[bad])}`;
|
|
2684
|
+
}
|
|
2685
|
+
if (expected === "number") {
|
|
2686
|
+
return Number.isInteger(value) && value >= 0
|
|
2687
|
+
? null
|
|
2688
|
+
: `must be a non-negative integer, got ${JSON.stringify(value)}`;
|
|
2689
|
+
}
|
|
2690
|
+
return typeof value === expected
|
|
2691
|
+
? null
|
|
2692
|
+
: `must be a ${expected}, got ${typeof value}`;
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2483
2695
|
/**
|
|
2484
2696
|
* Validate the policy block.
|
|
2485
2697
|
* @param {object} policy The policy block.
|
|
@@ -2522,10 +2734,9 @@ export function validatePolicy(policy) {
|
|
|
2522
2734
|
);
|
|
2523
2735
|
continue;
|
|
2524
2736
|
}
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
);
|
|
2737
|
+
const wrong = policyTypeProblem(expected, value);
|
|
2738
|
+
if (wrong) {
|
|
2739
|
+
problems.push(`policy.${section}.${field} ${wrong}`);
|
|
2529
2740
|
}
|
|
2530
2741
|
}
|
|
2531
2742
|
}
|
|
@@ -2586,7 +2797,7 @@ export function auditConfigKeys(config) {
|
|
|
2586
2797
|
* UNKNOWN, and an unknown manifest resolves exactly as it did before this
|
|
2587
2798
|
* option existed — a caller that has not been taught to read the manifest
|
|
2588
2799
|
* must not have its answers changed by silence.
|
|
2589
|
-
* @returns {Array<{id: string, level: string, mode: string, awaits: string|null, task: string|null, command: string|null, label: string, work: string|null, alias: {from: string, to: string}|null, evidence: {proof: string[], no_work: string[], on_hollow: string, wait_minutes: number|null, on_timeout: string}|null}>} Resolved provers, sorted by gate id.
|
|
2800
|
+
* @returns {Array<{id: string, level: string, mode: string, awaits: string|null, postedBy: number|null, task: string|null, command: string|null, label: string, work: string|null, alias: {from: string, to: string}|null, evidence: {proof: string[], no_work: string[], on_hollow: string, wait_minutes: number|null, on_timeout: string}|null}>} Resolved provers, sorted by gate id.
|
|
2590
2801
|
*/
|
|
2591
2802
|
export function resolveMoment({
|
|
2592
2803
|
gates,
|
|
@@ -2640,6 +2851,7 @@ export function resolveMoment({
|
|
|
2640
2851
|
level: "off",
|
|
2641
2852
|
mode: "off",
|
|
2642
2853
|
awaits: null,
|
|
2854
|
+
postedBy: null,
|
|
2643
2855
|
task: null,
|
|
2644
2856
|
command: null,
|
|
2645
2857
|
label: REGISTRY[id]?.label ?? id,
|
|
@@ -2682,6 +2894,7 @@ export function resolveMoment({
|
|
|
2682
2894
|
level: entry.level,
|
|
2683
2895
|
mode: entry.await ? "await" : intercepts ? "intercept" : "run",
|
|
2684
2896
|
awaits: entry.await ?? null,
|
|
2897
|
+
postedBy: entry.await ? (entry.posted_by ?? null) : null,
|
|
2685
2898
|
task: entry.await || intercepts ? null : task,
|
|
2686
2899
|
command: entry.await || intercepts || !task ? null : `${runner} ${task}`,
|
|
2687
2900
|
label: definition?.label ?? id,
|