@telorun/analyzer 0.34.0 → 0.35.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/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +0 -3
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/sources/manifest-cache.d.ts +48 -0
- package/dist/sources/manifest-cache.d.ts.map +1 -0
- package/dist/sources/manifest-cache.js +91 -0
- package/dist/sources/oci-ref.d.ts +21 -0
- package/dist/sources/oci-ref.d.ts.map +1 -0
- package/dist/sources/oci-ref.js +35 -0
- package/dist/validate-base-mapping.d.ts.map +1 -1
- package/dist/validate-base-mapping.js +8 -2
- package/dist/validate-throws-coverage.d.ts.map +1 -1
- package/dist/validate-throws-coverage.js +10 -3
- package/package.json +1 -1
- package/src/analyzer.ts +0 -4
- package/src/index.ts +10 -0
- package/src/sources/manifest-cache.ts +112 -0
- package/src/sources/oci-ref.ts +53 -0
- package/src/validate-base-mapping.ts +7 -2
- package/src/validate-throws-coverage.ts +14 -5
- package/dist/validate-kind-descriptions.d.ts +0 -20
- package/dist/validate-kind-descriptions.d.ts.map +0 -1
- package/dist/validate-kind-descriptions.js +0 -65
- package/src/validate-kind-descriptions.ts +0 -65
package/dist/analyzer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAKzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAoB9B,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAKzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAoB9B,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAuxB/F,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;gBAEzB,OAAO,GAAE,qBAA0B;IAI/C;;;;;;;;;;;;;;OAcG;IACH,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAgzBvB,aAAa,CACX,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAMvB,SAAS,CACP,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,gBAAgB,EAI1B,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,GACtC,gBAAgB,EAAE;IAmBrB,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,gBAAgB,GACzB;QAAE,WAAW,EAAE,kBAAkB,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;CAsB5F"}
|
package/dist/analyzer.js
CHANGED
|
@@ -22,7 +22,6 @@ import { buildEvalPaths, evalPathsCover } from "./eval-paths.js";
|
|
|
22
22
|
import { validateExtends } from "./validate-extends.js";
|
|
23
23
|
import { validateBaseMapping } from "./validate-base-mapping.js";
|
|
24
24
|
import { validateNestedInlineResources } from "./validate-nested-inline.js";
|
|
25
|
-
import { validateKindDescriptions } from "./validate-kind-descriptions.js";
|
|
26
25
|
import { validateProviderCoherence } from "./validate-provider-coherence.js";
|
|
27
26
|
import { validateReferences } from "./validate-references.js";
|
|
28
27
|
import { validateReferenceForms } from "./validate-reference-forms.js";
|
|
@@ -1383,8 +1382,6 @@ export class StaticAnalyzer {
|
|
|
1383
1382
|
diagnostics.push(...validateBaseMapping(allManifests, defs, aliases));
|
|
1384
1383
|
// Validate provider coherence rules for `provide:` template-target definitions.
|
|
1385
1384
|
diagnostics.push(...validateProviderCoherence(allManifests, defs, aliases));
|
|
1386
|
-
// Warn about exported kinds lacking a metadata.description (semantic-search input).
|
|
1387
|
-
diagnostics.push(...validateKindDescriptions(allManifests));
|
|
1388
1385
|
// Validate throws: declarations and catches: coverage (rules 1, 2, 4, 7)
|
|
1389
1386
|
diagnostics.push(...validateThrowsCoverage(allManifests, defs, aliases, this.celEnv, aliasesByModule, rootModules));
|
|
1390
1387
|
// Warn about declared variables / secrets / ports that no CEL references.
|
package/dist/index.d.ts
CHANGED
|
@@ -28,6 +28,10 @@ export { defaultSources } from "./sources/default-sources.js";
|
|
|
28
28
|
export { splitIntegrity, foldIntegrity, verifyIntegrity, verifiedFetch, sha256Base64Url, IntegrityError, } from "./sources/integrity.js";
|
|
29
29
|
export { parseModuleRef, isRegistryRef } from "./sources/module-ref.js";
|
|
30
30
|
export type { ParsedModuleRef } from "./sources/module-ref.js";
|
|
31
|
+
export { OCI_SCHEME, isOciRef, parseOciRef } from "./sources/oci-ref.js";
|
|
32
|
+
export type { ParsedOciRef } from "./sources/oci-ref.js";
|
|
33
|
+
export { MANIFEST_CACHE_BASE_URL, ManifestCacheSource, manifestCacheKey, manifestCacheUrl, ociManifestCacheCoords, } from "./sources/manifest-cache.js";
|
|
34
|
+
export type { ManifestCacheCoords } from "./sources/manifest-cache.js";
|
|
31
35
|
export { withSyntheticPositions } from "./with-synthetic-positions.js";
|
|
32
36
|
export { DEFAULT_MANIFEST_FILENAME, DiagnosticSeverity } from "./types.js";
|
|
33
37
|
export type { AnalysisDiagnostic, AnalysisOptions, LoaderInitOptions, LoadOptions, ManifestSource, Position, PositionIndex, Range } from "./types.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -17,5 +17,7 @@ export { RegistrySource } from "./sources/registry-source.js";
|
|
|
17
17
|
export { defaultSources } from "./sources/default-sources.js";
|
|
18
18
|
export { splitIntegrity, foldIntegrity, verifyIntegrity, verifiedFetch, sha256Base64Url, IntegrityError, } from "./sources/integrity.js";
|
|
19
19
|
export { parseModuleRef, isRegistryRef } from "./sources/module-ref.js";
|
|
20
|
+
export { OCI_SCHEME, isOciRef, parseOciRef } from "./sources/oci-ref.js";
|
|
21
|
+
export { MANIFEST_CACHE_BASE_URL, ManifestCacheSource, manifestCacheKey, manifestCacheUrl, ociManifestCacheCoords, } from "./sources/manifest-cache.js";
|
|
20
22
|
export { withSyntheticPositions } from "./with-synthetic-positions.js";
|
|
21
23
|
export { DEFAULT_MANIFEST_FILENAME, DiagnosticSeverity } from "./types.js";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ManifestSource } from "../types.js";
|
|
2
|
+
/** The hub's static manifest cache: an R2 bucket bound directly to the domain,
|
|
3
|
+
* serving each tracked module version's `telo.yaml` at a deterministic key.
|
|
4
|
+
* Read plane only — never artifact payloads; install/run stay origin-direct. */
|
|
5
|
+
export declare const MANIFEST_CACHE_BASE_URL = "https://manifests.telo.sh";
|
|
6
|
+
/** The location identity of one cached module version. Mirrors what the hub
|
|
7
|
+
* stores per version — `{ transport, host, path, version }` — so the tracker's
|
|
8
|
+
* write key and the editor's read key come from the same function and never
|
|
9
|
+
* drift. `path` is the slash-separated repo/module path (multi-segment OCI
|
|
10
|
+
* repos nest as prefixes). */
|
|
11
|
+
export interface ManifestCacheCoords {
|
|
12
|
+
transport: string;
|
|
13
|
+
host: string;
|
|
14
|
+
path: string;
|
|
15
|
+
version: string;
|
|
16
|
+
}
|
|
17
|
+
/** Deterministic cache key for one module version:
|
|
18
|
+
* `<transport>/<host>/<path…>/<version>/telo.yaml`. Returns `null` when any
|
|
19
|
+
* coordinate is empty or would traverse out of the key space. */
|
|
20
|
+
export declare function manifestCacheKey(coords: ManifestCacheCoords): string | null;
|
|
21
|
+
/** Cache coordinates for an `oci://host/repo@tag` ref. Returns `null` when the
|
|
22
|
+
* ref carries no explicit tag (a defaulted `latest` or a `sha256:` digest is
|
|
23
|
+
* not addressable — the cache is keyed by the human version tag the tracker
|
|
24
|
+
* enumerated). The inline `#sha256-…` fragment is tolerated and ignored. */
|
|
25
|
+
export declare function ociManifestCacheCoords(ref: string): ManifestCacheCoords | null;
|
|
26
|
+
/** Full cache URL for a ref or coordinates, or `null` when not addressable. */
|
|
27
|
+
export declare function manifestCacheUrl(refOrCoords: string | ManifestCacheCoords, baseUrl?: string): string | null;
|
|
28
|
+
/** Resolves `oci://` module refs against the hub's static manifest cache with a
|
|
29
|
+
* plain CORS GET — a browser can't speak the OCI protocol, so this is the
|
|
30
|
+
* browser-safe read path for OCI imports. A pinned ref (`#sha256-…`) is
|
|
31
|
+
* verified against the fetched bytes, so a compromised cache can't mislead
|
|
32
|
+
* analysis; an unpinned ref is analyzed on trust — the security boundary is
|
|
33
|
+
* install/run (origin-direct, re-verified), not edit time. */
|
|
34
|
+
export declare class ManifestCacheSource implements ManifestSource {
|
|
35
|
+
private readonly baseUrl;
|
|
36
|
+
constructor(baseUrl?: string);
|
|
37
|
+
supports(url: string): boolean;
|
|
38
|
+
read(ref: string): Promise<{
|
|
39
|
+
text: string;
|
|
40
|
+
source: string;
|
|
41
|
+
}>;
|
|
42
|
+
/** Mirror of the kernel OCI transport's resolution: normalize the repo to a
|
|
43
|
+
* directory base so `../lib` under `oci://ghcr.io/aws/my-app` →
|
|
44
|
+
* `oci://ghcr.io/aws/lib`. The tag is dropped — the caller re-pins from the
|
|
45
|
+
* sibling's own version. Non-relative refs pass through. */
|
|
46
|
+
resolveRelative(base: string, relative: string): string;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=manifest-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest-cache.d.ts","sourceRoot":"","sources":["../../src/sources/manifest-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAKlD;;iFAEiF;AACjF,eAAO,MAAM,uBAAuB,8BAA8B,CAAC;AAEnE;;;;+BAI+B;AAC/B,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAOD;;kEAEkE;AAClE,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,mBAAmB,GAAG,MAAM,GAAG,IAAI,CAO3E;AAED;;;6EAG6E;AAC7E,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,mBAAmB,GAAG,IAAI,CAY9E;AAID,+EAA+E;AAC/E,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,GAAG,mBAAmB,EACzC,OAAO,GAAE,MAAgC,GACxC,MAAM,GAAG,IAAI,CAKf;AAED;;;;;+DAK+D;AAC/D,qBAAa,mBAAoB,YAAW,cAAc;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,GAAE,MAAgC;IAEtE,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAQxB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAalE;;;iEAG6D;IAC7D,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;CAOxD"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { DEFAULT_MANIFEST_FILENAME } from "../types.js";
|
|
2
|
+
import { splitIntegrity, verifiedFetch } from "./integrity.js";
|
|
3
|
+
import { OCI_SCHEME, isOciRef, parseOciRef } from "./oci-ref.js";
|
|
4
|
+
/** The hub's static manifest cache: an R2 bucket bound directly to the domain,
|
|
5
|
+
* serving each tracked module version's `telo.yaml` at a deterministic key.
|
|
6
|
+
* Read plane only — never artifact payloads; install/run stay origin-direct. */
|
|
7
|
+
export const MANIFEST_CACHE_BASE_URL = "https://manifests.telo.sh";
|
|
8
|
+
/** True for a segment that would corrupt or escape the cache key space. */
|
|
9
|
+
function invalidSegment(segment) {
|
|
10
|
+
return segment === "" || segment === "." || segment === ".." || segment.includes("\\");
|
|
11
|
+
}
|
|
12
|
+
/** Deterministic cache key for one module version:
|
|
13
|
+
* `<transport>/<host>/<path…>/<version>/telo.yaml`. Returns `null` when any
|
|
14
|
+
* coordinate is empty or would traverse out of the key space. */
|
|
15
|
+
export function manifestCacheKey(coords) {
|
|
16
|
+
const { transport, host, path, version } = coords;
|
|
17
|
+
const segments = [transport, host, ...path.split("/"), version];
|
|
18
|
+
if (segments.some(invalidSegment) || transport.includes("/") || host.includes("/") || version.includes("/")) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
return `${segments.join("/")}/${DEFAULT_MANIFEST_FILENAME}`;
|
|
22
|
+
}
|
|
23
|
+
/** Cache coordinates for an `oci://host/repo@tag` ref. Returns `null` when the
|
|
24
|
+
* ref carries no explicit tag (a defaulted `latest` or a `sha256:` digest is
|
|
25
|
+
* not addressable — the cache is keyed by the human version tag the tracker
|
|
26
|
+
* enumerated). The inline `#sha256-…` fragment is tolerated and ignored. */
|
|
27
|
+
export function ociManifestCacheCoords(ref) {
|
|
28
|
+
if (!isOciRef(ref))
|
|
29
|
+
return null;
|
|
30
|
+
let parsed;
|
|
31
|
+
try {
|
|
32
|
+
parsed = parseOciRef(ref);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const { base } = splitIntegrity(ref);
|
|
38
|
+
const explicitTag = base.endsWith(`@${parsed.reference}`) && !parsed.reference.includes(":");
|
|
39
|
+
if (!explicitTag)
|
|
40
|
+
return null;
|
|
41
|
+
return { transport: "oci", host: parsed.host, path: parsed.repo, version: parsed.reference };
|
|
42
|
+
}
|
|
43
|
+
/** Full cache URL for a ref or coordinates, or `null` when not addressable. */
|
|
44
|
+
export function manifestCacheUrl(refOrCoords, baseUrl = MANIFEST_CACHE_BASE_URL) {
|
|
45
|
+
const coords = typeof refOrCoords === "string" ? ociManifestCacheCoords(refOrCoords) : refOrCoords;
|
|
46
|
+
if (!coords)
|
|
47
|
+
return null;
|
|
48
|
+
const key = manifestCacheKey(coords);
|
|
49
|
+
return key ? `${baseUrl.replace(/\/+$/, "")}/${key}` : null;
|
|
50
|
+
}
|
|
51
|
+
/** Resolves `oci://` module refs against the hub's static manifest cache with a
|
|
52
|
+
* plain CORS GET — a browser can't speak the OCI protocol, so this is the
|
|
53
|
+
* browser-safe read path for OCI imports. A pinned ref (`#sha256-…`) is
|
|
54
|
+
* verified against the fetched bytes, so a compromised cache can't mislead
|
|
55
|
+
* analysis; an unpinned ref is analyzed on trust — the security boundary is
|
|
56
|
+
* install/run (origin-direct, re-verified), not edit time. */
|
|
57
|
+
export class ManifestCacheSource {
|
|
58
|
+
baseUrl;
|
|
59
|
+
constructor(baseUrl = MANIFEST_CACHE_BASE_URL) {
|
|
60
|
+
this.baseUrl = baseUrl;
|
|
61
|
+
}
|
|
62
|
+
supports(url) {
|
|
63
|
+
// Claim every oci:// ref, addressable or not — nothing else browser-safe
|
|
64
|
+
// can ever resolve one, so an untagged/digest ref must reach `read()`'s
|
|
65
|
+
// actionable error instead of falling through to a file-not-found from a
|
|
66
|
+
// local/HTTP source.
|
|
67
|
+
return isOciRef(url);
|
|
68
|
+
}
|
|
69
|
+
async read(ref) {
|
|
70
|
+
const { integrity } = splitIntegrity(ref);
|
|
71
|
+
const coords = ociManifestCacheCoords(ref);
|
|
72
|
+
const fetchUrl = coords && manifestCacheUrl(coords, this.baseUrl);
|
|
73
|
+
if (!fetchUrl) {
|
|
74
|
+
throw new Error(`Cannot resolve '${ref}' from the manifest cache — an oci:// import needs an explicit version tag (oci://host/repo@version)`);
|
|
75
|
+
}
|
|
76
|
+
const { text } = await verifiedFetch(fetchUrl, integrity, ref);
|
|
77
|
+
return { text, source: `${OCI_SCHEME}${coords.host}/${coords.path}@${coords.version}` };
|
|
78
|
+
}
|
|
79
|
+
/** Mirror of the kernel OCI transport's resolution: normalize the repo to a
|
|
80
|
+
* directory base so `../lib` under `oci://ghcr.io/aws/my-app` →
|
|
81
|
+
* `oci://ghcr.io/aws/lib`. The tag is dropped — the caller re-pins from the
|
|
82
|
+
* sibling's own version. Non-relative refs pass through. */
|
|
83
|
+
resolveRelative(base, relative) {
|
|
84
|
+
if (!relative.startsWith(".") && !relative.startsWith("/"))
|
|
85
|
+
return relative;
|
|
86
|
+
const { host, repo } = parseOciRef(base);
|
|
87
|
+
const resolved = new URL(relative, `https://${host}/${repo}/`);
|
|
88
|
+
const newRepo = resolved.pathname.replace(/^\/+/, "");
|
|
89
|
+
return `${OCI_SCHEME}${host}/${newRepo}`;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare const OCI_SCHEME = "oci://";
|
|
2
|
+
/** A parsed `oci://host/repo@reference` module ref.
|
|
3
|
+
*
|
|
4
|
+
* - `host` — the registry host (`ghcr.io`, `123.dkr.ecr.us-east-1.amazonaws.com`).
|
|
5
|
+
* - `repo` — the repository path (`aws/telo-s3`), possibly multi-segment.
|
|
6
|
+
* - `reference` — a tag (`1.2.0`) or a digest (`sha256:...`); the OCI address.
|
|
7
|
+
* - `integrity` — Telo's inline `sha256-<base64url>` hash when the ref is pinned
|
|
8
|
+
* (authoritative across transports; the OCI digest is only corroborating). */
|
|
9
|
+
export interface ParsedOciRef {
|
|
10
|
+
host: string;
|
|
11
|
+
repo: string;
|
|
12
|
+
reference: string;
|
|
13
|
+
integrity?: string;
|
|
14
|
+
}
|
|
15
|
+
/** True when `ref` uses the `oci://` scheme (integrity fragment tolerated). */
|
|
16
|
+
export declare function isOciRef(ref: string): boolean;
|
|
17
|
+
/** Parse `oci://host/repo@reference[#sha256-...]`. Throws on a malformed ref.
|
|
18
|
+
* A `reference` may be a tag or a `sha256:` digest; when absent (`@` omitted)
|
|
19
|
+
* it defaults to `latest`, matching OCI tooling. */
|
|
20
|
+
export declare function parseOciRef(ref: string): ParsedOciRef;
|
|
21
|
+
//# sourceMappingURL=oci-ref.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oci-ref.d.ts","sourceRoot":"","sources":["../../src/sources/oci-ref.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,UAAU,WAAW,CAAC;AAEnC;;;;;;iFAMiF;AACjF,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,+EAA+E;AAC/E,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE7C;AAED;;qDAEqD;AACrD,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CA0BrD"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { splitIntegrity } from "./integrity.js";
|
|
2
|
+
export const OCI_SCHEME = "oci://";
|
|
3
|
+
/** True when `ref` uses the `oci://` scheme (integrity fragment tolerated). */
|
|
4
|
+
export function isOciRef(ref) {
|
|
5
|
+
return splitIntegrity(ref).base.startsWith(OCI_SCHEME);
|
|
6
|
+
}
|
|
7
|
+
/** Parse `oci://host/repo@reference[#sha256-...]`. Throws on a malformed ref.
|
|
8
|
+
* A `reference` may be a tag or a `sha256:` digest; when absent (`@` omitted)
|
|
9
|
+
* it defaults to `latest`, matching OCI tooling. */
|
|
10
|
+
export function parseOciRef(ref) {
|
|
11
|
+
const { base, integrity } = splitIntegrity(ref);
|
|
12
|
+
if (!base.startsWith(OCI_SCHEME)) {
|
|
13
|
+
throw new Error(`Invalid OCI reference '${ref}', expected oci://host/repo@reference`);
|
|
14
|
+
}
|
|
15
|
+
const rest = base.slice(OCI_SCHEME.length);
|
|
16
|
+
const slash = rest.indexOf("/");
|
|
17
|
+
if (slash <= 0) {
|
|
18
|
+
throw new Error(`Invalid OCI reference '${ref}', missing repository path after host`);
|
|
19
|
+
}
|
|
20
|
+
const host = rest.slice(0, slash);
|
|
21
|
+
let repoAndRef = rest.slice(slash + 1);
|
|
22
|
+
let reference = "latest";
|
|
23
|
+
const at = repoAndRef.lastIndexOf("@");
|
|
24
|
+
// A digest reference is `repo@sha256:...` — the `@` before `sha256:` is the
|
|
25
|
+
// separator, not part of the repo. A tag reference has no `@`.
|
|
26
|
+
if (at > 0) {
|
|
27
|
+
reference = repoAndRef.slice(at + 1);
|
|
28
|
+
repoAndRef = repoAndRef.slice(0, at);
|
|
29
|
+
}
|
|
30
|
+
const repo = repoAndRef;
|
|
31
|
+
if (!host || !repo || !reference) {
|
|
32
|
+
throw new Error(`Invalid OCI reference '${ref}', expected oci://host/repo@reference`);
|
|
33
|
+
}
|
|
34
|
+
return { host, repo, reference, integrity };
|
|
35
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-base-mapping.d.ts","sourceRoot":"","sources":["../src/validate-base-mapping.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"validate-base-mapping.d.ts","sourceRoot":"","sources":["../src/validate-base-mapping.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAEnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAmBzE;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,GACrB,kBAAkB,EAAE,CAwCtB"}
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { isCompiledValue } from "@telorun/sdk";
|
|
2
|
+
import { isTaggedSentinel } from "@telorun/templating";
|
|
2
3
|
import { effectiveAuthorSchema, resolveParent } from "./extends-resolution.js";
|
|
3
4
|
import { DiagnosticSeverity } from "./types.js";
|
|
4
5
|
const SOURCE = "telo-analyzer";
|
|
5
|
-
/** True when a value subtree contains a
|
|
6
|
-
*
|
|
6
|
+
/** True when a value subtree contains a CEL leaf — either a compiled CEL value
|
|
7
|
+
* (post-precompile) or the raw `!cel` tagged sentinel the analyzer sees at
|
|
8
|
+
* `telo check` time (this validator runs on the un-precompiled tree). Such a
|
|
9
|
+
* value can produce anything at runtime, so its type is not statically
|
|
10
|
+
* checkable. */
|
|
7
11
|
function containsCel(value) {
|
|
8
12
|
if (isCompiledValue(value))
|
|
9
13
|
return true;
|
|
14
|
+
if (isTaggedSentinel(value) && value.engine === "cel")
|
|
15
|
+
return true;
|
|
10
16
|
if (Array.isArray(value))
|
|
11
17
|
return value.some(containsCel);
|
|
12
18
|
if (value && typeof value === "object") {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-throws-coverage.d.ts","sourceRoot":"","sources":["../src/validate-throws-coverage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,WAAW,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"validate-throws-coverage.d.ts","sourceRoot":"","sources":["../src/validate-throws-coverage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAA0B,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAOnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAoezE,oDAAoD;AACpD,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,WAAW,EAChB,eAAe,GAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAa,EACvD,WAAW,GAAE,GAAG,CAAC,MAAM,CAAa,GACnC,kBAAkB,EAAE,CAkDtB"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isTaggedSentinel } from "@telorun/templating";
|
|
1
2
|
import { scopeResolverForModule } from "./alias-resolver.js";
|
|
2
3
|
import { createResolveCtx, resolveThrowsUnion, } from "./resolve-throws-union.js";
|
|
3
4
|
import { DiagnosticSeverity } from "./types.js";
|
|
@@ -87,12 +88,18 @@ function resolveHandlerRef(sibling) {
|
|
|
87
88
|
* Parenthesised nestings of `||` over equality/in are flattened.
|
|
88
89
|
* Any non-matching sub-expression forfeits coverage for the whole `when:`. */
|
|
89
90
|
function extractCoveredCodes(whenExpr, env) {
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
// The `when:` value is either a `!cel` TaggedSentinel (carrying the raw CEL
|
|
92
|
+
// source) or an inline `"${{ … }}"` string — both forms are load-equivalent.
|
|
93
|
+
const source = isTaggedSentinel(whenExpr) && whenExpr.engine === "cel"
|
|
94
|
+
? whenExpr.source
|
|
95
|
+
: typeof whenExpr === "string"
|
|
96
|
+
? whenExpr.match(/\$\{\{\s*([^}]+?)\s*\}\}/)?.[1]
|
|
97
|
+
: undefined;
|
|
98
|
+
if (!source)
|
|
92
99
|
return { proven: false, codes: new Set() };
|
|
93
100
|
let ast;
|
|
94
101
|
try {
|
|
95
|
-
ast = env.parse(
|
|
102
|
+
ast = env.parse(source.trim()).ast;
|
|
96
103
|
}
|
|
97
104
|
catch {
|
|
98
105
|
return { proven: false, codes: new Set() };
|
package/package.json
CHANGED
package/src/analyzer.ts
CHANGED
|
@@ -42,7 +42,6 @@ import { buildEvalPaths, evalPathsCover } from "./eval-paths.js";
|
|
|
42
42
|
import { validateExtends } from "./validate-extends.js";
|
|
43
43
|
import { validateBaseMapping } from "./validate-base-mapping.js";
|
|
44
44
|
import { validateNestedInlineResources } from "./validate-nested-inline.js";
|
|
45
|
-
import { validateKindDescriptions } from "./validate-kind-descriptions.js";
|
|
46
45
|
import { validateProviderCoherence } from "./validate-provider-coherence.js";
|
|
47
46
|
import { validateReferences } from "./validate-references.js";
|
|
48
47
|
import { validateReferenceForms } from "./validate-reference-forms.js";
|
|
@@ -1654,9 +1653,6 @@ export class StaticAnalyzer {
|
|
|
1654
1653
|
// Validate provider coherence rules for `provide:` template-target definitions.
|
|
1655
1654
|
diagnostics.push(...validateProviderCoherence(allManifests, defs, aliases));
|
|
1656
1655
|
|
|
1657
|
-
// Warn about exported kinds lacking a metadata.description (semantic-search input).
|
|
1658
|
-
diagnostics.push(...validateKindDescriptions(allManifests));
|
|
1659
|
-
|
|
1660
1656
|
// Validate throws: declarations and catches: coverage (rules 1, 2, 4, 7)
|
|
1661
1657
|
diagnostics.push(
|
|
1662
1658
|
...validateThrowsCoverage(allManifests, defs, aliases, this.celEnv, aliasesByModule, rootModules),
|
package/src/index.ts
CHANGED
|
@@ -75,6 +75,16 @@ export {
|
|
|
75
75
|
} from "./sources/integrity.js";
|
|
76
76
|
export { parseModuleRef, isRegistryRef } from "./sources/module-ref.js";
|
|
77
77
|
export type { ParsedModuleRef } from "./sources/module-ref.js";
|
|
78
|
+
export { OCI_SCHEME, isOciRef, parseOciRef } from "./sources/oci-ref.js";
|
|
79
|
+
export type { ParsedOciRef } from "./sources/oci-ref.js";
|
|
80
|
+
export {
|
|
81
|
+
MANIFEST_CACHE_BASE_URL,
|
|
82
|
+
ManifestCacheSource,
|
|
83
|
+
manifestCacheKey,
|
|
84
|
+
manifestCacheUrl,
|
|
85
|
+
ociManifestCacheCoords,
|
|
86
|
+
} from "./sources/manifest-cache.js";
|
|
87
|
+
export type { ManifestCacheCoords } from "./sources/manifest-cache.js";
|
|
78
88
|
export { withSyntheticPositions } from "./with-synthetic-positions.js";
|
|
79
89
|
export { DEFAULT_MANIFEST_FILENAME, DiagnosticSeverity } from "./types.js";
|
|
80
90
|
export type {
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { ManifestSource } from "../types.js";
|
|
2
|
+
import { DEFAULT_MANIFEST_FILENAME } from "../types.js";
|
|
3
|
+
import { splitIntegrity, verifiedFetch } from "./integrity.js";
|
|
4
|
+
import { OCI_SCHEME, isOciRef, parseOciRef } from "./oci-ref.js";
|
|
5
|
+
|
|
6
|
+
/** The hub's static manifest cache: an R2 bucket bound directly to the domain,
|
|
7
|
+
* serving each tracked module version's `telo.yaml` at a deterministic key.
|
|
8
|
+
* Read plane only — never artifact payloads; install/run stay origin-direct. */
|
|
9
|
+
export const MANIFEST_CACHE_BASE_URL = "https://manifests.telo.sh";
|
|
10
|
+
|
|
11
|
+
/** The location identity of one cached module version. Mirrors what the hub
|
|
12
|
+
* stores per version — `{ transport, host, path, version }` — so the tracker's
|
|
13
|
+
* write key and the editor's read key come from the same function and never
|
|
14
|
+
* drift. `path` is the slash-separated repo/module path (multi-segment OCI
|
|
15
|
+
* repos nest as prefixes). */
|
|
16
|
+
export interface ManifestCacheCoords {
|
|
17
|
+
transport: string;
|
|
18
|
+
host: string;
|
|
19
|
+
path: string;
|
|
20
|
+
version: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** True for a segment that would corrupt or escape the cache key space. */
|
|
24
|
+
function invalidSegment(segment: string): boolean {
|
|
25
|
+
return segment === "" || segment === "." || segment === ".." || segment.includes("\\");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Deterministic cache key for one module version:
|
|
29
|
+
* `<transport>/<host>/<path…>/<version>/telo.yaml`. Returns `null` when any
|
|
30
|
+
* coordinate is empty or would traverse out of the key space. */
|
|
31
|
+
export function manifestCacheKey(coords: ManifestCacheCoords): string | null {
|
|
32
|
+
const { transport, host, path, version } = coords;
|
|
33
|
+
const segments = [transport, host, ...path.split("/"), version];
|
|
34
|
+
if (segments.some(invalidSegment) || transport.includes("/") || host.includes("/") || version.includes("/")) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return `${segments.join("/")}/${DEFAULT_MANIFEST_FILENAME}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Cache coordinates for an `oci://host/repo@tag` ref. Returns `null` when the
|
|
41
|
+
* ref carries no explicit tag (a defaulted `latest` or a `sha256:` digest is
|
|
42
|
+
* not addressable — the cache is keyed by the human version tag the tracker
|
|
43
|
+
* enumerated). The inline `#sha256-…` fragment is tolerated and ignored. */
|
|
44
|
+
export function ociManifestCacheCoords(ref: string): ManifestCacheCoords | null {
|
|
45
|
+
if (!isOciRef(ref)) return null;
|
|
46
|
+
let parsed: ParsedOciRefShape;
|
|
47
|
+
try {
|
|
48
|
+
parsed = parseOciRef(ref);
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const { base } = splitIntegrity(ref);
|
|
53
|
+
const explicitTag = base.endsWith(`@${parsed.reference}`) && !parsed.reference.includes(":");
|
|
54
|
+
if (!explicitTag) return null;
|
|
55
|
+
return { transport: "oci", host: parsed.host, path: parsed.repo, version: parsed.reference };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type ParsedOciRefShape = ReturnType<typeof parseOciRef>;
|
|
59
|
+
|
|
60
|
+
/** Full cache URL for a ref or coordinates, or `null` when not addressable. */
|
|
61
|
+
export function manifestCacheUrl(
|
|
62
|
+
refOrCoords: string | ManifestCacheCoords,
|
|
63
|
+
baseUrl: string = MANIFEST_CACHE_BASE_URL,
|
|
64
|
+
): string | null {
|
|
65
|
+
const coords = typeof refOrCoords === "string" ? ociManifestCacheCoords(refOrCoords) : refOrCoords;
|
|
66
|
+
if (!coords) return null;
|
|
67
|
+
const key = manifestCacheKey(coords);
|
|
68
|
+
return key ? `${baseUrl.replace(/\/+$/, "")}/${key}` : null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Resolves `oci://` module refs against the hub's static manifest cache with a
|
|
72
|
+
* plain CORS GET — a browser can't speak the OCI protocol, so this is the
|
|
73
|
+
* browser-safe read path for OCI imports. A pinned ref (`#sha256-…`) is
|
|
74
|
+
* verified against the fetched bytes, so a compromised cache can't mislead
|
|
75
|
+
* analysis; an unpinned ref is analyzed on trust — the security boundary is
|
|
76
|
+
* install/run (origin-direct, re-verified), not edit time. */
|
|
77
|
+
export class ManifestCacheSource implements ManifestSource {
|
|
78
|
+
constructor(private readonly baseUrl: string = MANIFEST_CACHE_BASE_URL) {}
|
|
79
|
+
|
|
80
|
+
supports(url: string): boolean {
|
|
81
|
+
// Claim every oci:// ref, addressable or not — nothing else browser-safe
|
|
82
|
+
// can ever resolve one, so an untagged/digest ref must reach `read()`'s
|
|
83
|
+
// actionable error instead of falling through to a file-not-found from a
|
|
84
|
+
// local/HTTP source.
|
|
85
|
+
return isOciRef(url);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async read(ref: string): Promise<{ text: string; source: string }> {
|
|
89
|
+
const { integrity } = splitIntegrity(ref);
|
|
90
|
+
const coords = ociManifestCacheCoords(ref);
|
|
91
|
+
const fetchUrl = coords && manifestCacheUrl(coords, this.baseUrl);
|
|
92
|
+
if (!fetchUrl) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`Cannot resolve '${ref}' from the manifest cache — an oci:// import needs an explicit version tag (oci://host/repo@version)`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
const { text } = await verifiedFetch(fetchUrl, integrity, ref);
|
|
98
|
+
return { text, source: `${OCI_SCHEME}${coords.host}/${coords.path}@${coords.version}` };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Mirror of the kernel OCI transport's resolution: normalize the repo to a
|
|
102
|
+
* directory base so `../lib` under `oci://ghcr.io/aws/my-app` →
|
|
103
|
+
* `oci://ghcr.io/aws/lib`. The tag is dropped — the caller re-pins from the
|
|
104
|
+
* sibling's own version. Non-relative refs pass through. */
|
|
105
|
+
resolveRelative(base: string, relative: string): string {
|
|
106
|
+
if (!relative.startsWith(".") && !relative.startsWith("/")) return relative;
|
|
107
|
+
const { host, repo } = parseOciRef(base);
|
|
108
|
+
const resolved = new URL(relative, `https://${host}/${repo}/`);
|
|
109
|
+
const newRepo = resolved.pathname.replace(/^\/+/, "");
|
|
110
|
+
return `${OCI_SCHEME}${host}/${newRepo}`;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { splitIntegrity } from "./integrity.js";
|
|
2
|
+
|
|
3
|
+
export const OCI_SCHEME = "oci://";
|
|
4
|
+
|
|
5
|
+
/** A parsed `oci://host/repo@reference` module ref.
|
|
6
|
+
*
|
|
7
|
+
* - `host` — the registry host (`ghcr.io`, `123.dkr.ecr.us-east-1.amazonaws.com`).
|
|
8
|
+
* - `repo` — the repository path (`aws/telo-s3`), possibly multi-segment.
|
|
9
|
+
* - `reference` — a tag (`1.2.0`) or a digest (`sha256:...`); the OCI address.
|
|
10
|
+
* - `integrity` — Telo's inline `sha256-<base64url>` hash when the ref is pinned
|
|
11
|
+
* (authoritative across transports; the OCI digest is only corroborating). */
|
|
12
|
+
export interface ParsedOciRef {
|
|
13
|
+
host: string;
|
|
14
|
+
repo: string;
|
|
15
|
+
reference: string;
|
|
16
|
+
integrity?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** True when `ref` uses the `oci://` scheme (integrity fragment tolerated). */
|
|
20
|
+
export function isOciRef(ref: string): boolean {
|
|
21
|
+
return splitIntegrity(ref).base.startsWith(OCI_SCHEME);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Parse `oci://host/repo@reference[#sha256-...]`. Throws on a malformed ref.
|
|
25
|
+
* A `reference` may be a tag or a `sha256:` digest; when absent (`@` omitted)
|
|
26
|
+
* it defaults to `latest`, matching OCI tooling. */
|
|
27
|
+
export function parseOciRef(ref: string): ParsedOciRef {
|
|
28
|
+
const { base, integrity } = splitIntegrity(ref);
|
|
29
|
+
if (!base.startsWith(OCI_SCHEME)) {
|
|
30
|
+
throw new Error(`Invalid OCI reference '${ref}', expected oci://host/repo@reference`);
|
|
31
|
+
}
|
|
32
|
+
const rest = base.slice(OCI_SCHEME.length);
|
|
33
|
+
const slash = rest.indexOf("/");
|
|
34
|
+
if (slash <= 0) {
|
|
35
|
+
throw new Error(`Invalid OCI reference '${ref}', missing repository path after host`);
|
|
36
|
+
}
|
|
37
|
+
const host = rest.slice(0, slash);
|
|
38
|
+
let repoAndRef = rest.slice(slash + 1);
|
|
39
|
+
|
|
40
|
+
let reference = "latest";
|
|
41
|
+
const at = repoAndRef.lastIndexOf("@");
|
|
42
|
+
// A digest reference is `repo@sha256:...` — the `@` before `sha256:` is the
|
|
43
|
+
// separator, not part of the repo. A tag reference has no `@`.
|
|
44
|
+
if (at > 0) {
|
|
45
|
+
reference = repoAndRef.slice(at + 1);
|
|
46
|
+
repoAndRef = repoAndRef.slice(0, at);
|
|
47
|
+
}
|
|
48
|
+
const repo = repoAndRef;
|
|
49
|
+
if (!host || !repo || !reference) {
|
|
50
|
+
throw new Error(`Invalid OCI reference '${ref}', expected oci://host/repo@reference`);
|
|
51
|
+
}
|
|
52
|
+
return { host, repo, reference, integrity };
|
|
53
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ResourceDefinition, ResourceManifest } from "@telorun/sdk";
|
|
2
2
|
import { isCompiledValue } from "@telorun/sdk";
|
|
3
|
+
import { isTaggedSentinel } from "@telorun/templating";
|
|
3
4
|
import type { AliasResolver } from "./alias-resolver.js";
|
|
4
5
|
import type { DefinitionRegistry } from "./definition-registry.js";
|
|
5
6
|
import { effectiveAuthorSchema, resolveParent, type DefResolver } from "./extends-resolution.js";
|
|
@@ -7,10 +8,14 @@ import { DiagnosticSeverity, type AnalysisDiagnostic } from "./types.js";
|
|
|
7
8
|
|
|
8
9
|
const SOURCE = "telo-analyzer";
|
|
9
10
|
|
|
10
|
-
/** True when a value subtree contains a
|
|
11
|
-
*
|
|
11
|
+
/** True when a value subtree contains a CEL leaf — either a compiled CEL value
|
|
12
|
+
* (post-precompile) or the raw `!cel` tagged sentinel the analyzer sees at
|
|
13
|
+
* `telo check` time (this validator runs on the un-precompiled tree). Such a
|
|
14
|
+
* value can produce anything at runtime, so its type is not statically
|
|
15
|
+
* checkable. */
|
|
12
16
|
function containsCel(value: unknown): boolean {
|
|
13
17
|
if (isCompiledValue(value)) return true;
|
|
18
|
+
if (isTaggedSentinel(value) && value.engine === "cel") return true;
|
|
14
19
|
if (Array.isArray(value)) return value.some(containsCel);
|
|
15
20
|
if (value && typeof value === "object") {
|
|
16
21
|
return Object.values(value as Record<string, unknown>).some(containsCel);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ASTNode, Environment } from "@marcbachmann/cel-js";
|
|
2
|
+
import { isTaggedSentinel } from "@telorun/templating";
|
|
2
3
|
import type { ResourceManifest } from "@telorun/sdk";
|
|
3
4
|
import { scopeResolverForModule, type AliasResolver } from "./alias-resolver.js";
|
|
4
5
|
import type { DefinitionRegistry } from "./definition-registry.js";
|
|
@@ -15,7 +16,8 @@ const SOURCE = "telo-analyzer";
|
|
|
15
16
|
const TEMPLATE_REGEX = /\$\{\{\s*([^}]+?)\s*\}\}/g;
|
|
16
17
|
|
|
17
18
|
interface OutcomeEntry {
|
|
18
|
-
|
|
19
|
+
/** Inline `"${{ … }}"` string or a `!cel` TaggedSentinel. */
|
|
20
|
+
when?: unknown;
|
|
19
21
|
body?: unknown;
|
|
20
22
|
headers?: Record<string, unknown>;
|
|
21
23
|
status?: number;
|
|
@@ -137,14 +139,21 @@ function resolveHandlerRef(sibling: unknown): { kind: string; name?: string } |
|
|
|
137
139
|
* Parenthesised nestings of `||` over equality/in are flattened.
|
|
138
140
|
* Any non-matching sub-expression forfeits coverage for the whole `when:`. */
|
|
139
141
|
function extractCoveredCodes(
|
|
140
|
-
whenExpr:
|
|
142
|
+
whenExpr: unknown,
|
|
141
143
|
env: Environment,
|
|
142
144
|
): { proven: boolean; codes: Set<string> } {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
+
// The `when:` value is either a `!cel` TaggedSentinel (carrying the raw CEL
|
|
146
|
+
// source) or an inline `"${{ … }}"` string — both forms are load-equivalent.
|
|
147
|
+
const source =
|
|
148
|
+
isTaggedSentinel(whenExpr) && whenExpr.engine === "cel"
|
|
149
|
+
? whenExpr.source
|
|
150
|
+
: typeof whenExpr === "string"
|
|
151
|
+
? whenExpr.match(/\$\{\{\s*([^}]+?)\s*\}\}/)?.[1]
|
|
152
|
+
: undefined;
|
|
153
|
+
if (!source) return { proven: false, codes: new Set() };
|
|
145
154
|
let ast: ASTNode;
|
|
146
155
|
try {
|
|
147
|
-
ast = env.parse(
|
|
156
|
+
ast = env.parse(source.trim()).ast;
|
|
148
157
|
} catch {
|
|
149
158
|
return { proven: false, codes: new Set() };
|
|
150
159
|
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { ResourceManifest } from "@telorun/sdk";
|
|
2
|
-
import { type AnalysisDiagnostic } from "./types.js";
|
|
3
|
-
/**
|
|
4
|
-
* Warns when a library exports a kind (via `exports.kinds`) whose local
|
|
5
|
-
* `Telo.Definition` carries no `metadata.description`.
|
|
6
|
-
*
|
|
7
|
-
* The description is the primary human text the federated-discovery hub embeds
|
|
8
|
-
* for semantic search (`search_resources`), so an exported kind without one is
|
|
9
|
-
* undiscoverable by meaning. A warning (not an error) so the stdlib backfill is
|
|
10
|
-
* incremental and CI isn't blocked mid-migration.
|
|
11
|
-
*
|
|
12
|
-
* Scope: only kinds a library *exports* and *defines locally* are checked.
|
|
13
|
-
* Re-exported kinds (`exports.kinds: [Alias.Kind]`) belong to their owning
|
|
14
|
-
* module and are skipped; an exported name that isn't a local Telo.Definition
|
|
15
|
-
* (e.g. a Telo.Abstract) is skipped too. The check keys off the root library's
|
|
16
|
-
* own module doc, which is only present when that library is analyzed directly
|
|
17
|
-
* — so importing an under-described library never leaks warnings to its consumer.
|
|
18
|
-
*/
|
|
19
|
-
export declare function validateKindDescriptions(manifests: ResourceManifest[]): AnalysisDiagnostic[];
|
|
20
|
-
//# sourceMappingURL=validate-kind-descriptions.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"validate-kind-descriptions.d.ts","sourceRoot":"","sources":["../src/validate-kind-descriptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIzE;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,gBAAgB,EAAE,GAAG,kBAAkB,EAAE,CA2C5F"}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { DiagnosticSeverity } from "./types.js";
|
|
2
|
-
const SOURCE = "telo-analyzer";
|
|
3
|
-
/**
|
|
4
|
-
* Warns when a library exports a kind (via `exports.kinds`) whose local
|
|
5
|
-
* `Telo.Definition` carries no `metadata.description`.
|
|
6
|
-
*
|
|
7
|
-
* The description is the primary human text the federated-discovery hub embeds
|
|
8
|
-
* for semantic search (`search_resources`), so an exported kind without one is
|
|
9
|
-
* undiscoverable by meaning. A warning (not an error) so the stdlib backfill is
|
|
10
|
-
* incremental and CI isn't blocked mid-migration.
|
|
11
|
-
*
|
|
12
|
-
* Scope: only kinds a library *exports* and *defines locally* are checked.
|
|
13
|
-
* Re-exported kinds (`exports.kinds: [Alias.Kind]`) belong to their owning
|
|
14
|
-
* module and are skipped; an exported name that isn't a local Telo.Definition
|
|
15
|
-
* (e.g. a Telo.Abstract) is skipped too. The check keys off the root library's
|
|
16
|
-
* own module doc, which is only present when that library is analyzed directly
|
|
17
|
-
* — so importing an under-described library never leaks warnings to its consumer.
|
|
18
|
-
*/
|
|
19
|
-
export function validateKindDescriptions(manifests) {
|
|
20
|
-
const diagnostics = [];
|
|
21
|
-
// Local Telo.Definition docs keyed by `<module>/<name>`.
|
|
22
|
-
const definitions = new Map();
|
|
23
|
-
for (const m of manifests) {
|
|
24
|
-
if (m.kind !== "Telo.Definition")
|
|
25
|
-
continue;
|
|
26
|
-
const name = m.metadata?.name;
|
|
27
|
-
const mod = m.metadata?.module;
|
|
28
|
-
if (name && mod)
|
|
29
|
-
definitions.set(`${mod}/${name}`, m);
|
|
30
|
-
}
|
|
31
|
-
for (const m of manifests) {
|
|
32
|
-
if (m.kind !== "Telo.Library")
|
|
33
|
-
continue;
|
|
34
|
-
const moduleName = m.metadata?.name;
|
|
35
|
-
if (!moduleName)
|
|
36
|
-
continue;
|
|
37
|
-
const exportedKinds = m.exports?.kinds;
|
|
38
|
-
if (!Array.isArray(exportedKinds))
|
|
39
|
-
continue;
|
|
40
|
-
for (const entry of exportedKinds) {
|
|
41
|
-
// Re-export (`Alias.Kind`) — the owning module owns its description.
|
|
42
|
-
if (typeof entry !== "string" || entry.includes("."))
|
|
43
|
-
continue;
|
|
44
|
-
const def = definitions.get(`${moduleName}/${entry}`);
|
|
45
|
-
if (!def)
|
|
46
|
-
continue; // not a local Telo.Definition (e.g. an abstract)
|
|
47
|
-
const description = def.metadata?.description;
|
|
48
|
-
if (typeof description === "string" && description.trim() !== "")
|
|
49
|
-
continue;
|
|
50
|
-
diagnostics.push({
|
|
51
|
-
severity: DiagnosticSeverity.Warning,
|
|
52
|
-
code: "KIND_MISSING_DESCRIPTION",
|
|
53
|
-
source: SOURCE,
|
|
54
|
-
message: `${moduleName}.${entry}: exported kind has no 'metadata.description'. Add a one-line ` +
|
|
55
|
-
`description — it is the primary text indexed for semantic discovery (search_resources).`,
|
|
56
|
-
data: {
|
|
57
|
-
resource: { kind: "Telo.Definition", name: entry },
|
|
58
|
-
filePath: def.metadata?.source,
|
|
59
|
-
path: "metadata.description",
|
|
60
|
-
},
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return diagnostics;
|
|
65
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import type { ResourceManifest } from "@telorun/sdk";
|
|
2
|
-
import { DiagnosticSeverity, type AnalysisDiagnostic } from "./types.js";
|
|
3
|
-
|
|
4
|
-
const SOURCE = "telo-analyzer";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Warns when a library exports a kind (via `exports.kinds`) whose local
|
|
8
|
-
* `Telo.Definition` carries no `metadata.description`.
|
|
9
|
-
*
|
|
10
|
-
* The description is the primary human text the federated-discovery hub embeds
|
|
11
|
-
* for semantic search (`search_resources`), so an exported kind without one is
|
|
12
|
-
* undiscoverable by meaning. A warning (not an error) so the stdlib backfill is
|
|
13
|
-
* incremental and CI isn't blocked mid-migration.
|
|
14
|
-
*
|
|
15
|
-
* Scope: only kinds a library *exports* and *defines locally* are checked.
|
|
16
|
-
* Re-exported kinds (`exports.kinds: [Alias.Kind]`) belong to their owning
|
|
17
|
-
* module and are skipped; an exported name that isn't a local Telo.Definition
|
|
18
|
-
* (e.g. a Telo.Abstract) is skipped too. The check keys off the root library's
|
|
19
|
-
* own module doc, which is only present when that library is analyzed directly
|
|
20
|
-
* — so importing an under-described library never leaks warnings to its consumer.
|
|
21
|
-
*/
|
|
22
|
-
export function validateKindDescriptions(manifests: ResourceManifest[]): AnalysisDiagnostic[] {
|
|
23
|
-
const diagnostics: AnalysisDiagnostic[] = [];
|
|
24
|
-
|
|
25
|
-
// Local Telo.Definition docs keyed by `<module>/<name>`.
|
|
26
|
-
const definitions = new Map<string, ResourceManifest>();
|
|
27
|
-
for (const m of manifests) {
|
|
28
|
-
if (m.kind !== "Telo.Definition") continue;
|
|
29
|
-
const name = m.metadata?.name as string | undefined;
|
|
30
|
-
const mod = (m.metadata as { module?: string } | undefined)?.module;
|
|
31
|
-
if (name && mod) definitions.set(`${mod}/${name}`, m);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
for (const m of manifests) {
|
|
35
|
-
if (m.kind !== "Telo.Library") continue;
|
|
36
|
-
const moduleName = m.metadata?.name as string | undefined;
|
|
37
|
-
if (!moduleName) continue;
|
|
38
|
-
const exportedKinds = (m as { exports?: { kinds?: unknown } }).exports?.kinds;
|
|
39
|
-
if (!Array.isArray(exportedKinds)) continue;
|
|
40
|
-
|
|
41
|
-
for (const entry of exportedKinds) {
|
|
42
|
-
// Re-export (`Alias.Kind`) — the owning module owns its description.
|
|
43
|
-
if (typeof entry !== "string" || entry.includes(".")) continue;
|
|
44
|
-
const def = definitions.get(`${moduleName}/${entry}`);
|
|
45
|
-
if (!def) continue; // not a local Telo.Definition (e.g. an abstract)
|
|
46
|
-
const description = (def.metadata as { description?: unknown } | undefined)?.description;
|
|
47
|
-
if (typeof description === "string" && description.trim() !== "") continue;
|
|
48
|
-
diagnostics.push({
|
|
49
|
-
severity: DiagnosticSeverity.Warning,
|
|
50
|
-
code: "KIND_MISSING_DESCRIPTION",
|
|
51
|
-
source: SOURCE,
|
|
52
|
-
message:
|
|
53
|
-
`${moduleName}.${entry}: exported kind has no 'metadata.description'. Add a one-line ` +
|
|
54
|
-
`description — it is the primary text indexed for semantic discovery (search_resources).`,
|
|
55
|
-
data: {
|
|
56
|
-
resource: { kind: "Telo.Definition", name: entry },
|
|
57
|
-
filePath: (def.metadata as { source?: string } | undefined)?.source,
|
|
58
|
-
path: "metadata.description",
|
|
59
|
-
},
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return diagnostics;
|
|
65
|
-
}
|