@juangcarmona/lighthouse 0.1.0 → 0.3.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/README.md +37 -7
- package/assets/guardrails/catalog.json +46 -0
- package/assets/schemas/guardrails.schema.json +41 -0
- package/assets/schemas/lighthouse-manifest.schema.json +23 -2
- package/assets/schemas/lighthouse-upgrade.schema.json +35 -0
- package/package.json +13 -2
- package/src/guardrails.mjs +140 -0
- package/src/lighthouse.mjs +831 -39
- package/upgrades/0.3.0.md +26 -0
package/README.md
CHANGED
|
@@ -4,17 +4,27 @@ This package is the core Lighthouse harness. It provides the `lighthouse` comman
|
|
|
4
4
|
|
|
5
5
|
## Initialize a repository
|
|
6
6
|
|
|
7
|
-
Install the exact Lighthouse package
|
|
7
|
+
Install the exact Lighthouse package artifact selected for the release and run `lighthouse init --name <product>` in a new repository. Lighthouse creates the manifest, the `src/`, `docs/`, `infra/` and `scripts/` boundaries, the repository guardrails and the generated `LIGHTHOUSE.md`. Inspect the plan and projection, then run `lighthouse verify` before adding project-specific policy or exceptions.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Use `lighthouse reconcile` to preview missing Lighthouse-managed paths in an existing adoption. It does not write by default; add `--apply` only after reviewing the plan. Existing files whose content differs from the generated projection are conflicts and are never overwritten by reconciliation.
|
|
10
|
+
|
|
11
|
+
Use `lighthouse upgrade` to preview a versioned foundation update. The command is dry-run by default, accepts `--scope harness`, `--scope sdlc` or `--scope all`, preserves customized projections and stops when ownership evidence is missing. Add `--apply` only after reviewing the plan, then run `lighthouse verify`.
|
|
12
|
+
|
|
13
|
+
The harness package owns the baseline command and verification contract. The SDLC package owns lifecycle skills, commands, metacommands, routes and gates, and `lighthouse init` records its selected version in `.lighthouse/sdlc.json`. The UI package owns visual tokens, native recipes and `DESIGN.md`. Stack profiles own their source templates, guidance and checks.
|
|
10
14
|
|
|
11
15
|
ProductShape, ArcShape and OpenSpec keep their own files and commands. Add them through their supported integration routes when the project needs them, and keep their versions and managed paths visible in the project configuration.
|
|
12
16
|
|
|
17
|
+
Use `lighthouse authorities` to prepare the two baseline authorities through their own CLIs. The command previews both plans by default; `--apply` runs ProductShape's full model initialization and ArcShape's architecture initialization without installing agent capabilities. Provider-owned files remain owned by the provider, and Lighthouse records the exact adopted versions in `lighthouse.json` only after both applications succeed.
|
|
18
|
+
|
|
19
|
+
The generated manifest enables the public-site route for greenfield repositories. Its build validates ProductShape and ArcShape before creating either snapshot; if the authorities are not ready, the build stops with the repair command instead of publishing a partial site.
|
|
20
|
+
|
|
21
|
+
Initialization reports missing ProductShape and ArcShape authority inputs as adoption gaps. It does not copy or replace their provider-owned files. The SDLC package remains provider-neutral; an agent host decides where its skills and commands are projected.
|
|
22
|
+
|
|
13
23
|
## New repository
|
|
14
24
|
|
|
15
|
-
1.
|
|
16
|
-
2.
|
|
17
|
-
3.
|
|
25
|
+
1. Install the harness and the profile artifacts required by the application.
|
|
26
|
+
2. Run `lighthouse init --name <product>` and review the proposed repository boundaries.
|
|
27
|
+
3. Review the generated manifest and `LIGHTHOUSE.md`, then run `lighthouse verify`.
|
|
18
28
|
4. Add the selected profile checks to the repository verification command and CI workflow.
|
|
19
29
|
5. Record project-owned exceptions with an owner and review date.
|
|
20
30
|
|
|
@@ -22,12 +32,32 @@ ProductShape, ArcShape and OpenSpec keep their own files and commands. Add them
|
|
|
22
32
|
|
|
23
33
|
1. Inventory the current source layout, tools, managed files and CI before editing.
|
|
24
34
|
2. Map existing files to the selected Lighthouse contracts and identify collisions.
|
|
25
|
-
3. Adopt the manifest and verifier first;
|
|
35
|
+
3. Adopt the manifest and verifier first; the initializer reports conflicts and preserves product-owned guidance and generated outputs.
|
|
26
36
|
4. Add profiles one at a time and measure their checks before making a workflow status required.
|
|
27
37
|
5. Record each exception in the project-owned documentation and remove it when the product is ready.
|
|
28
38
|
|
|
29
39
|
## Current command scope
|
|
30
40
|
|
|
31
|
-
The
|
|
41
|
+
The command supports `init`, `authorities`, `reconcile`, `upgrade` and `verify`. It checks the manifest, repository boundaries, Conventional Commits metadata, branch naming, the selected SDLC package projection and generated content. Lifecycle stages, tracker operations, agent runs and CodeGraph checks require their own configured integrations.
|
|
42
|
+
|
|
43
|
+
Initialization installs `.husky/commit-msg`, `.husky/pre-push`, `scripts/lighthouse-guard.mjs` and `.github/workflows/lighthouse.yml` when those paths are free. Existing files remain project-owned and are reported as conflicts. Reconciliation can add missing managed paths after adoption, but it does not overwrite a path that has drifted or belongs to another tool.
|
|
44
|
+
|
|
45
|
+
The upgrade route records the ownership hashes in `.lighthouse/upgrade.json`. A missing or stale state file is an adoption gap; reconcile it first instead of allowing a full re-adoption. Upgrade plans report the source and target package and baseline, selected scopes, classifications, preserved paths, verification and recovery action.
|
|
32
46
|
|
|
33
47
|
The package does not contain the repository gallery, provider-owned integrations or reference source. Those remain with the source repository or the profile that owns them.
|
|
48
|
+
|
|
49
|
+
## Guardrail catalog
|
|
50
|
+
|
|
51
|
+
The CLI package owns a versioned catalog under `assets/guardrails/`; it describes the guardrails already present in the Lighthouse baseline and their applicability, level, kind, verification method and repair hint.
|
|
52
|
+
|
|
53
|
+
`level` and `kind` are independent: `level` is `required`, `recommended` or `optional`, while `kind` is `hard` or `soft`; neither dimension selects the verification method.
|
|
54
|
+
|
|
55
|
+
`lighthouse verify` keeps its existing human-readable success and failure output; `lighthouse verify --format json` exposes the same run for agents and CI without changing the default route.
|
|
56
|
+
|
|
57
|
+
The catalog uses `passed` when evidence satisfies a check, `failed` when a deterministic check finds a problem, `drift` when managed content differs, `unavailable` when a declared checker cannot run or produce evidence, `unconfigured` when an applicable check or integration is not enabled, `pending-review` when semantic or human judgement is still required, and `not-applicable` when the selected baseline or profiles do not include the guardrail.
|
|
58
|
+
|
|
59
|
+
Only `passed` and `not-applicable` satisfy a required guardrail; `pending-review` is not evidence of compliance and is distinct from both `unconfigured` and `unavailable`.
|
|
60
|
+
|
|
61
|
+
To add a future guardrail, the catalog owner adds a JSON entry under the packaged `assets/guardrails/` surface, validates it against `assets/schemas/guardrails.schema.json`, declares its profile applicability and repair hint, and uses an existing checker or an explicit semantic or human verification method; in this delivery the surface belongs to the CLI package, and the CLI core does not change.
|
|
62
|
+
|
|
63
|
+
A new deterministic checker mechanism or a new engineering practice is outside this catalog and requires a separate Product Change.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../schemas/guardrails.schema.json",
|
|
3
|
+
"schemaVersion": 1,
|
|
4
|
+
"guardrails": [
|
|
5
|
+
{
|
|
6
|
+
"id": "foundation.manifest",
|
|
7
|
+
"owner": "lighthouse",
|
|
8
|
+
"appliesTo": { "baseline": true, "profiles": [] },
|
|
9
|
+
"level": "required",
|
|
10
|
+
"kind": "hard",
|
|
11
|
+
"verification": "deterministic",
|
|
12
|
+
"check": "manifest",
|
|
13
|
+
"failureHint": "Repair lighthouse.json and run lighthouse verify again."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "repository.layout",
|
|
17
|
+
"owner": "lighthouse",
|
|
18
|
+
"appliesTo": { "baseline": true, "profiles": [] },
|
|
19
|
+
"level": "required",
|
|
20
|
+
"kind": "hard",
|
|
21
|
+
"verification": "deterministic",
|
|
22
|
+
"check": "layout",
|
|
23
|
+
"failureHint": "Create the declared src/, docs/, infra/ and scripts/ repository boundaries."
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"id": "repository.managed-projection",
|
|
27
|
+
"owner": "lighthouse",
|
|
28
|
+
"appliesTo": { "baseline": true, "profiles": [] },
|
|
29
|
+
"level": "required",
|
|
30
|
+
"kind": "hard",
|
|
31
|
+
"verification": "deterministic",
|
|
32
|
+
"check": "managed-projection",
|
|
33
|
+
"failureHint": "Run lighthouse reconcile after reviewing the managed path plan."
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"id": "repository.branch-convention",
|
|
37
|
+
"owner": "lighthouse",
|
|
38
|
+
"appliesTo": { "baseline": true, "profiles": [] },
|
|
39
|
+
"level": "required",
|
|
40
|
+
"kind": "hard",
|
|
41
|
+
"verification": "deterministic",
|
|
42
|
+
"check": "branch",
|
|
43
|
+
"failureHint": "Use the branch pattern declared in lighthouse.json."
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/juangcarmona/lighthouse/schemas/guardrails.schema.json",
|
|
4
|
+
"title": "Lighthouse guardrail catalog",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["$schema", "schemaVersion", "guardrails"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"$schema": { "type": "string" },
|
|
10
|
+
"schemaVersion": { "const": 1 },
|
|
11
|
+
"guardrails": {
|
|
12
|
+
"type": "array",
|
|
13
|
+
"items": { "$ref": "#/$defs/guardrail" }
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"$defs": {
|
|
17
|
+
"guardrail": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"additionalProperties": false,
|
|
20
|
+
"required": ["id", "owner", "appliesTo", "level", "kind", "verification", "check", "failureHint"],
|
|
21
|
+
"properties": {
|
|
22
|
+
"id": { "type": "string", "pattern": "^[a-z0-9]+(?:[.-][a-z0-9]+)*$" },
|
|
23
|
+
"owner": { "type": "string", "minLength": 1 },
|
|
24
|
+
"appliesTo": {
|
|
25
|
+
"type": "object",
|
|
26
|
+
"additionalProperties": false,
|
|
27
|
+
"required": ["baseline", "profiles"],
|
|
28
|
+
"properties": {
|
|
29
|
+
"baseline": { "type": "boolean" },
|
|
30
|
+
"profiles": { "type": "array", "items": { "type": "string" }, "uniqueItems": true }
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"level": { "enum": ["required", "recommended", "optional"] },
|
|
34
|
+
"kind": { "enum": ["hard", "soft"] },
|
|
35
|
+
"verification": { "enum": ["deterministic", "semantic", "human"] },
|
|
36
|
+
"check": { "type": "string", "minLength": 1 },
|
|
37
|
+
"failureHint": { "type": "string", "minLength": 1 }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -11,8 +11,29 @@
|
|
|
11
11
|
"baseline": {"const": "0.1.0"},
|
|
12
12
|
"product": {"type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9 ._-]{0,63}$"},
|
|
13
13
|
"profiles": {"type": "array", "items": {"enum": ["node-tooling", "dotnet-backend", "visual-theme"]}, "uniqueItems": true},
|
|
14
|
-
"integrations": {"type": "object", "additionalProperties": false, "required": ["productShape", "arcShape"
|
|
15
|
-
"
|
|
14
|
+
"integrations": {"type": "object", "additionalProperties": false, "required": ["productShape", "arcShape"], "properties": {"productShape": {"$ref": "#/$defs/pin"}, "arcShape": {"$ref": "#/$defs/pin"}}},
|
|
15
|
+
"packages": {"type": "object", "additionalProperties": false, "required": ["sdlc"], "properties": {"sdlc": {"type": "object", "additionalProperties": false, "required": ["name", "version"], "properties": {"name": {"const": "@juangcarmona/lighthouse-sdlc"}, "version": {"$ref": "#/$defs/pin"}}}}},
|
|
16
|
+
"managed": {"anyOf": [
|
|
17
|
+
{"const": ["LIGHTHOUSE.md"]},
|
|
18
|
+
{"const": ["LIGHTHOUSE.md", "scripts/lighthouse-guard.mjs", ".husky/commit-msg", ".husky/pre-push", ".github/workflows/lighthouse.yml"]},
|
|
19
|
+
{"const": ["LIGHTHOUSE.md", "scripts/lighthouse-guard.mjs", ".husky/commit-msg", ".husky/pre-push", ".github/workflows/lighthouse.yml", ".lighthouse/sdlc.json"]},
|
|
20
|
+
{"const": ["LIGHTHOUSE.md", "scripts/lighthouse-guard.mjs", ".husky/commit-msg", ".husky/pre-push", ".github/workflows/lighthouse.yml", ".lighthouse/sdlc.json", ".lighthouse/upgrade.json"]}
|
|
21
|
+
]},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "object",
|
|
24
|
+
"additionalProperties": false,
|
|
25
|
+
"required": ["layout", "publicSite", "conventions"],
|
|
26
|
+
"properties": {
|
|
27
|
+
"layout": {"const": ["src", "docs", "infra", "scripts"]},
|
|
28
|
+
"publicSite": {"type": "object", "additionalProperties": false, "required": ["enabled"], "properties": {"enabled": {"type": "boolean"}}},
|
|
29
|
+
"conventions": {
|
|
30
|
+
"type": "object",
|
|
31
|
+
"additionalProperties": false,
|
|
32
|
+
"required": ["commits", "branchPattern"],
|
|
33
|
+
"properties": {"commits": {"const": "conventional"}, "branchPattern": {"type": "string", "minLength": 1}}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
16
37
|
},
|
|
17
38
|
"$defs": {
|
|
18
39
|
"pin": {"type": ["string", "null"], "pattern": "^(?:\\d+\\.\\d+\\.\\d+|[a-f0-9]{40})$"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/juangcarmona/lighthouse/schemas/lighthouse-upgrade.schema.json",
|
|
4
|
+
"title": "Lighthouse upgrade ownership state",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["schemaVersion", "package", "baseline", "capabilities", "projections", "lastAppliedMigration", "files"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"$schema": {"type": "string"},
|
|
10
|
+
"schemaVersion": {"const": 1},
|
|
11
|
+
"package": {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"additionalProperties": false,
|
|
14
|
+
"required": ["name", "version"],
|
|
15
|
+
"properties": {"name": {"const": "@juangcarmona/lighthouse"}, "version": {"type": "string", "pattern": "^(?:\\d+\\.\\d+\\.\\d+|[a-f0-9]{40})$"}}
|
|
16
|
+
},
|
|
17
|
+
"baseline": {"type": "string", "minLength": 1},
|
|
18
|
+
"capabilities": {"$ref": "#/$defs/versions"},
|
|
19
|
+
"projections": {"$ref": "#/$defs/versions"},
|
|
20
|
+
"lastAppliedMigration": {"type": "string", "minLength": 1},
|
|
21
|
+
"files": {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"additionalProperties": {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"additionalProperties": false,
|
|
26
|
+
"required": ["hash", "scope"],
|
|
27
|
+
"properties": {
|
|
28
|
+
"hash": {"type": "string", "pattern": "^[a-f0-9]{64}$"},
|
|
29
|
+
"scope": {"enum": ["harness", "sdlc"]}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"$defs": {"versions": {"type": "object", "additionalProperties": {"type": ["string", "null"]}}}
|
|
35
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juangcarmona/lighthouse",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Lighthouse adoption and verification CLI",
|
|
6
6
|
"repository": {
|
|
@@ -19,7 +19,18 @@
|
|
|
19
19
|
"engines": {
|
|
20
20
|
"node": ">=22"
|
|
21
21
|
},
|
|
22
|
-
"
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@arcshape/cli": "0.1.3",
|
|
24
|
+
"@prodshape/cli": "0.22.0",
|
|
25
|
+
"@juangcarmona/lighthouse-sdlc": "0.3.0"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"src/lighthouse.mjs",
|
|
29
|
+
"src/guardrails.mjs",
|
|
30
|
+
"assets",
|
|
31
|
+
"README.md",
|
|
32
|
+
"upgrades"
|
|
33
|
+
],
|
|
23
34
|
"bin": {
|
|
24
35
|
"lighthouse": "./src/lighthouse.mjs"
|
|
25
36
|
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const catalogDirectory = fileURLToPath(new URL('../assets/guardrails/', import.meta.url));
|
|
6
|
+
const allowedLevels = new Set(['required', 'recommended', 'optional']);
|
|
7
|
+
const allowedKinds = new Set(['hard', 'soft']);
|
|
8
|
+
const allowedVerification = new Set(['deterministic', 'semantic', 'human']);
|
|
9
|
+
const allowedStatuses = new Set(['passed', 'failed', 'drift', 'unavailable', 'unconfigured', 'pending-review', 'not-applicable']);
|
|
10
|
+
|
|
11
|
+
function descriptorProblems(descriptor) {
|
|
12
|
+
const problems = [];
|
|
13
|
+
if (!descriptor || typeof descriptor !== 'object' || Array.isArray(descriptor)) return ['descriptor must be an object'];
|
|
14
|
+
for (const key of ['id', 'owner', 'appliesTo', 'level', 'kind', 'verification', 'check', 'failureHint']) {
|
|
15
|
+
if (!(key in descriptor)) problems.push(`missing ${key}`);
|
|
16
|
+
}
|
|
17
|
+
if (typeof descriptor.id !== 'string' || !/^[a-z0-9]+(?:[.-][a-z0-9]+)*$/u.test(descriptor.id)) problems.push('id must use lowercase dot or hyphen notation');
|
|
18
|
+
if (typeof descriptor.owner !== 'string' || !descriptor.owner) problems.push('owner must be a non-empty string');
|
|
19
|
+
if (!descriptor.appliesTo || typeof descriptor.appliesTo !== 'object' || Array.isArray(descriptor.appliesTo)
|
|
20
|
+
|| typeof descriptor.appliesTo.baseline !== 'boolean' || !Array.isArray(descriptor.appliesTo.profiles)
|
|
21
|
+
|| descriptor.appliesTo.profiles.some((profile) => typeof profile !== 'string')) {
|
|
22
|
+
problems.push('appliesTo must declare baseline and profile names');
|
|
23
|
+
}
|
|
24
|
+
if (!allowedLevels.has(descriptor.level)) problems.push('level must be required, recommended or optional');
|
|
25
|
+
if (!allowedKinds.has(descriptor.kind)) problems.push('kind must be hard or soft');
|
|
26
|
+
if (!allowedVerification.has(descriptor.verification)) problems.push('verification must be deterministic, semantic or human');
|
|
27
|
+
if (typeof descriptor.check !== 'string' || !descriptor.check) problems.push('check must be a non-empty string');
|
|
28
|
+
if (typeof descriptor.failureHint !== 'string' || !descriptor.failureHint) problems.push('failureHint must be a non-empty string');
|
|
29
|
+
return problems;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function catalogProblems(catalog) {
|
|
33
|
+
const problems = [];
|
|
34
|
+
if (!catalog || typeof catalog !== 'object' || Array.isArray(catalog)) return ['catalog must be an object'];
|
|
35
|
+
if (catalog.schemaVersion !== 1) problems.push('schemaVersion must be 1');
|
|
36
|
+
if (!Array.isArray(catalog.guardrails)) problems.push('guardrails must be an array');
|
|
37
|
+
const ids = new Set();
|
|
38
|
+
for (const descriptor of catalog.guardrails ?? []) {
|
|
39
|
+
problems.push(...descriptorProblems(descriptor).map((problem) => `${descriptor?.id ?? '<unknown>'}: ${problem}`));
|
|
40
|
+
if (descriptor?.id && ids.has(descriptor.id)) problems.push(`${descriptor.id}: duplicate id`);
|
|
41
|
+
if (descriptor?.id) ids.add(descriptor.id);
|
|
42
|
+
}
|
|
43
|
+
return problems;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function readGuardrailCatalog() {
|
|
47
|
+
const files = readdirSync(catalogDirectory).filter((file) => file.endsWith('.json')).sort();
|
|
48
|
+
const catalogs = files.map((file) => JSON.parse(readFileSync(join(catalogDirectory, file), 'utf8')));
|
|
49
|
+
const catalog = {
|
|
50
|
+
schemaVersion: 1,
|
|
51
|
+
guardrails: catalogs.flatMap((item) => item.guardrails ?? []),
|
|
52
|
+
};
|
|
53
|
+
const problems = catalogProblems(catalog);
|
|
54
|
+
if (problems.length) throw new Error(`Invalid guardrail catalog:\n${problems.join('\n')}`);
|
|
55
|
+
return catalog;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function initialReviewStatus(descriptor) {
|
|
59
|
+
return descriptor.verification === 'deterministic' ? 'unavailable' : 'pending-review';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function appliesTo(descriptor, manifest) {
|
|
63
|
+
const selectedProfiles = new Set(manifest.profiles ?? []);
|
|
64
|
+
return descriptor.appliesTo.baseline || descriptor.appliesTo.profiles.some((profile) => selectedProfiles.has(profile));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function result(descriptor, status, evidence = [], problems = []) {
|
|
68
|
+
return {
|
|
69
|
+
id: descriptor.id,
|
|
70
|
+
owner: descriptor.owner,
|
|
71
|
+
level: descriptor.level,
|
|
72
|
+
kind: descriptor.kind,
|
|
73
|
+
verification: descriptor.verification,
|
|
74
|
+
status,
|
|
75
|
+
evidence,
|
|
76
|
+
problems,
|
|
77
|
+
nextAction: status === 'passed' || status === 'not-applicable' ? null : descriptor.failureHint,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function runDeterministicCheck(descriptor, context) {
|
|
82
|
+
if (descriptor.check === 'manifest') return result(descriptor, 'passed', [{ path: 'lighthouse.json' }]);
|
|
83
|
+
if (descriptor.check === 'layout') {
|
|
84
|
+
const missing = [];
|
|
85
|
+
for (const directory of context.repository.layout) {
|
|
86
|
+
if (!(await context.isDirectory(context.resolve(directory)))) missing.push(`missing repository boundary ${directory}/; run lighthouse init`);
|
|
87
|
+
}
|
|
88
|
+
return result(descriptor, missing.length ? 'failed' : 'passed', context.repository.layout.map((directory) => ({ path: `${directory}/` })), missing);
|
|
89
|
+
}
|
|
90
|
+
if (descriptor.check === 'managed-projection') {
|
|
91
|
+
const missing = [];
|
|
92
|
+
const drifted = [];
|
|
93
|
+
for (const [file, expected] of Object.entries(context.managedFiles)) {
|
|
94
|
+
const target = context.resolve(file);
|
|
95
|
+
if (!(await context.exists(target))) missing.push(`${file} is missing; run lighthouse init after reviewing the plan`);
|
|
96
|
+
else if (await context.readFile(target, 'utf8') !== expected) drifted.push(`${file} has drifted; regenerate it from lighthouse.json`);
|
|
97
|
+
}
|
|
98
|
+
const problems = [...missing, ...drifted];
|
|
99
|
+
return result(descriptor, drifted.length ? 'drift' : missing.length ? 'failed' : 'passed', Object.keys(context.managedFiles).map((path) => ({ path })), problems);
|
|
100
|
+
}
|
|
101
|
+
if (descriptor.check === 'branch') {
|
|
102
|
+
const problems = context.validateGitConventions(context.repository);
|
|
103
|
+
return result(descriptor, problems.length ? 'failed' : 'passed', [{ value: context.branch ?? '' }, { pattern: context.repository.conventions.branchPattern }], problems);
|
|
104
|
+
}
|
|
105
|
+
return result(descriptor, 'unavailable', [], [`No deterministic checker is registered for ${descriptor.id}.`]);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function evaluateGuardrails({ catalog, manifest, manifestProblems, repository, managedFiles, resolve, exists, isDirectory, readFile, validateGitConventions, branch }) {
|
|
109
|
+
if (manifestProblems.length) {
|
|
110
|
+
const descriptor = catalog.guardrails.find((item) => item.check === 'manifest') ?? catalog.guardrails[0];
|
|
111
|
+
const failed = result(descriptor, 'failed', [{ path: 'lighthouse.json' }], manifestProblems.map((problem) => `Invalid lighthouse.json: ${problem}`));
|
|
112
|
+
return report([failed]);
|
|
113
|
+
}
|
|
114
|
+
const context = { manifest, repository, managedFiles, resolve, exists, isDirectory, readFile, validateGitConventions, branch };
|
|
115
|
+
const results = [];
|
|
116
|
+
for (const descriptor of catalog.guardrails) {
|
|
117
|
+
if (!appliesTo(descriptor, manifest)) {
|
|
118
|
+
results.push(result(descriptor, 'not-applicable'));
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (descriptor.verification !== 'deterministic') {
|
|
122
|
+
results.push(result(descriptor, initialReviewStatus(descriptor)));
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
results.push(await runDeterministicCheck(descriptor, context));
|
|
126
|
+
}
|
|
127
|
+
return report(results);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function report(results) {
|
|
131
|
+
const blocking = results.filter((item) => item.level === 'required' && !['passed', 'not-applicable'].includes(item.status));
|
|
132
|
+
const summary = Object.fromEntries([...allowedStatuses].map((status) => [status, results.filter((item) => item.status === status).length]));
|
|
133
|
+
return {
|
|
134
|
+
schemaVersion: 1,
|
|
135
|
+
results,
|
|
136
|
+
summary,
|
|
137
|
+
blocking: blocking.map((item) => item.id),
|
|
138
|
+
problems: blocking.flatMap((item) => item.problems),
|
|
139
|
+
};
|
|
140
|
+
}
|