@warpgogol/werkstatt-shared 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +30 -14
- package/src/checks/index.ts +1 -0
- package/src/checks/result-helpers.ts +60 -6
- package/src/checks/suppressions-config.ts +26 -1
- package/src/content/system-manifest.ts +8 -0
- package/src/ontology/archetypes/index.json +17 -1
- package/src/ontology/schemas/page-entry.ts +5 -7
- package/src/ontology/schemas/system/manifest.ts +8 -0
- package/src/ontology/schemas/system/verification.ts +40 -0
- package/src/passport/sign.ts +20 -14
- package/src/share/agent/ard-catalog.ts +133 -0
- package/src/share/agent/index.ts +1 -0
- package/src/share/content-reference.ts +7 -1
- package/src/share/middleware/access-protection.ts +174 -0
- package/src/share/middleware/tests/access-protection.test.ts +123 -0
- package/src/share/page.ts +4 -2
- package/src/share/routes/template-filter.ts +36 -0
- package/src/share/routes/tests/template-filter.test.ts +40 -0
- package/src/share/scripts/lenis.ts +1 -0
- package/src/share/semantic/block-extractors/index.ts +24 -0
- package/src/share/semantic/build-page.ts +39 -1
- package/src/share/semantic/extract.ts +85 -11
- package/src/share/semantic/ids.ts +13 -0
- package/src/share/semantic/jsonld/organization.ts +20 -0
- package/src/share/semantic/jsonld/video.ts +56 -0
- package/src/share/semantic/jsonld/webpage.ts +1 -1
- package/src/share/semantic/jsonld.ts +5 -0
- package/src/share/semantic/models.ts +9 -0
- package/src/share/semantic/organization-profile.ts +3 -2
- package/src/share/semantic/page-utils.ts +4 -10
- package/src/share/semantic/tests/split-sentences.test.ts +97 -0
- package/src/share/slug/heading-slugger.ts +26 -0
- package/src/share/slug/index.ts +15 -0
- package/src/share/slug/slug-id.ts +22 -0
- package/src/share/slug/slug-url.ts +24 -0
- package/src/share/slug/strategies.ts +68 -0
- package/src/share/slug/tests/slug.test.ts +84 -0
- package/src/share/tests/ard-catalog.test.ts +129 -0
- package/src/share/tests/build-page-price-markers.test.ts +3 -0
- package/src/share/tests/jsonld-video.test.ts +143 -0
- package/src/share/tests/jsonld-webpage.test.ts +69 -0
- package/src/share/tests/organization-jsonld.test.ts +52 -0
- package/src/share/types/axiom-study.d.ts +22 -0
- package/src/share/utility-registry.yaml +36 -0
- package/src/surface/blueprint-schema.ts +1 -1
- package/src/surface/blueprint-types.ts +1 -1
- package/tsconfig.json +11 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@warpgogol/werkstatt-shared",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -250,6 +250,10 @@
|
|
|
250
250
|
"types": "./src/share/middleware/language-redirect.ts",
|
|
251
251
|
"default": "./src/share/middleware/language-redirect.ts"
|
|
252
252
|
},
|
|
253
|
+
"./share/middleware/access-protection": {
|
|
254
|
+
"types": "./src/share/middleware/access-protection.ts",
|
|
255
|
+
"default": "./src/share/middleware/access-protection.ts"
|
|
256
|
+
},
|
|
253
257
|
"./share/offer-capacity": {
|
|
254
258
|
"types": "./src/share/offer-capacity.ts",
|
|
255
259
|
"default": "./src/share/offer-capacity.ts"
|
|
@@ -318,6 +322,14 @@
|
|
|
318
322
|
"types": "./src/share/shared-context.ts",
|
|
319
323
|
"default": "./src/share/shared-context.ts"
|
|
320
324
|
},
|
|
325
|
+
"./share/slug": {
|
|
326
|
+
"types": "./src/share/slug/index.ts",
|
|
327
|
+
"default": "./src/share/slug/index.ts"
|
|
328
|
+
},
|
|
329
|
+
"./share/routes/template-filter": {
|
|
330
|
+
"types": "./src/share/routes/template-filter.ts",
|
|
331
|
+
"default": "./src/share/routes/template-filter.ts"
|
|
332
|
+
},
|
|
321
333
|
"./share/string-utils": {
|
|
322
334
|
"types": "./src/share/string-utils.ts",
|
|
323
335
|
"default": "./src/share/string-utils.ts"
|
|
@@ -1331,15 +1343,6 @@
|
|
|
1331
1343
|
"default": "./src/share/scripts/index.ts"
|
|
1332
1344
|
}
|
|
1333
1345
|
},
|
|
1334
|
-
"scripts": {
|
|
1335
|
-
"lint": "pnpm exec eslint \"src/**/*.ts\"",
|
|
1336
|
-
"typecheck": "pnpm exec tsc -p tsconfig.json --noEmit",
|
|
1337
|
-
"build": "pnpm exec tsc -p tsconfig.json --noEmit",
|
|
1338
|
-
"build:check": "pnpm exec tsc -p tsconfig.json --noEmit",
|
|
1339
|
-
"prepublishOnly": "pnpm exec tsc -p tsconfig.json --noEmit",
|
|
1340
|
-
"test": "vitest run",
|
|
1341
|
-
"test:watch": "vitest"
|
|
1342
|
-
},
|
|
1343
1346
|
"publishConfig": {
|
|
1344
1347
|
"access": "public"
|
|
1345
1348
|
},
|
|
@@ -1349,10 +1352,13 @@
|
|
|
1349
1352
|
],
|
|
1350
1353
|
"dependencies": {
|
|
1351
1354
|
"@lordicon/element": "^2.3.1",
|
|
1352
|
-
"@
|
|
1355
|
+
"@warpgogol/werkstatt": "*",
|
|
1353
1356
|
"ajv": "^8.20.0",
|
|
1354
1357
|
"bs58": "^6.0.0",
|
|
1358
|
+
"@sindresorhus/slugify": "^3.0.0",
|
|
1359
|
+
"cyrillic-to-translit-js": "^3.2.1",
|
|
1355
1360
|
"expr-eval": "^2.0.2",
|
|
1361
|
+
"github-slugger": "^2.0.0",
|
|
1356
1362
|
"hls.js": "^1.6.16",
|
|
1357
1363
|
"lenis": "^1.3.25",
|
|
1358
1364
|
"plyr": "^3.8.4",
|
|
@@ -1361,17 +1367,27 @@
|
|
|
1361
1367
|
"zod": "^4.4.3"
|
|
1362
1368
|
},
|
|
1363
1369
|
"optionalDependencies": {
|
|
1364
|
-
"@syrokomskyi/axiom-study": "
|
|
1370
|
+
"@syrokomskyi/axiom-study": "link:../../../pipelines/packages/axiom/axiom-study"
|
|
1365
1371
|
},
|
|
1366
1372
|
"devDependencies": {
|
|
1373
|
+
"@astrojs/markdown-remark": "^7.0.0",
|
|
1367
1374
|
"@cloudflare/workers-types": "^5.20260801.1",
|
|
1368
1375
|
"@types/node": "^24.0.0",
|
|
1376
|
+
"@warpgogol/werkstatt": "*",
|
|
1369
1377
|
"astro": "^7.2.0",
|
|
1370
1378
|
"eslint": "^10.8.0",
|
|
1371
1379
|
"fast-check": "^4.9.0",
|
|
1380
|
+
"gsap": "^3.15.0",
|
|
1372
1381
|
"typescript": "^6.0.3",
|
|
1373
1382
|
"typescript-eslint": "8.65.0",
|
|
1374
1383
|
"vitest": "^4.1.10"
|
|
1375
1384
|
},
|
|
1376
|
-
"
|
|
1377
|
-
|
|
1385
|
+
"scripts": {
|
|
1386
|
+
"lint": "pnpm exec eslint \"src/**/*.ts\"",
|
|
1387
|
+
"typecheck": "pnpm exec tsc -p tsconfig.json --noEmit",
|
|
1388
|
+
"build": "pnpm exec tsc -p tsconfig.json --noEmit",
|
|
1389
|
+
"build:check": "pnpm exec tsc -p tsconfig.json --noEmit",
|
|
1390
|
+
"test": "vitest run",
|
|
1391
|
+
"test:watch": "vitest"
|
|
1392
|
+
}
|
|
1393
|
+
}
|
package/src/checks/index.ts
CHANGED
|
@@ -16,16 +16,55 @@ ruleId = command; diagnosticsResult takes rich Diagnostic[] with registered ids.
|
|
|
16
16
|
</CHANGE_SUMMARY>
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import type {
|
|
19
|
+
import type {
|
|
20
|
+
CheckResult,
|
|
21
|
+
Diagnostic,
|
|
22
|
+
KernelCommandResult,
|
|
23
|
+
KernelNextStep,
|
|
24
|
+
} from "@warpgogol/werkstatt/kernel";
|
|
25
|
+
|
|
26
|
+
function defaultFailNextSteps(command: string): KernelNextStep[] {
|
|
27
|
+
return [
|
|
28
|
+
{
|
|
29
|
+
action: `Fix the error diagnostics reported by ${command} above, then re-run the pipeline`,
|
|
30
|
+
kind: "required",
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Format error/warning counts with proper pluralization and zero-omission.
|
|
37
|
+
*
|
|
38
|
+
* - `0, 0` → `""` (empty string — caller should omit)
|
|
39
|
+
* - `1, 0` → `"1 error"`
|
|
40
|
+
* - `0, 1` → `"1 warning"`
|
|
41
|
+
* - `2, 0` → `"2 errors"`
|
|
42
|
+
* - `2, 1` → `"2 errors, 1 warning"`
|
|
43
|
+
*/
|
|
44
|
+
export function formatCounts(errors: number, warnings: number): string {
|
|
45
|
+
const parts: string[] = [];
|
|
46
|
+
if (errors > 0) {
|
|
47
|
+
parts.push(`${errors} error${errors === 1 ? "" : "s"}`);
|
|
48
|
+
}
|
|
49
|
+
if (warnings > 0) {
|
|
50
|
+
parts.push(`${warnings} warning${warnings === 1 ? "" : "s"}`);
|
|
51
|
+
}
|
|
52
|
+
return parts.join(", ");
|
|
53
|
+
}
|
|
20
54
|
|
|
21
55
|
/**
|
|
22
56
|
* RFC-0203: canonical result builder. Migrated checks emit `Diagnostic[]` with
|
|
23
57
|
* registered rule ids instead of bare strings. Exit code is 1 when any
|
|
24
58
|
* diagnostic is an error; warnings and info do not fail the pipeline.
|
|
59
|
+
*
|
|
60
|
+
* When `nextSteps` are not provided and the result has errors, a default
|
|
61
|
+
* agent-actionable nextStep is emitted: "Fix the error diagnostics … then
|
|
62
|
+
* re-run the pipeline".
|
|
25
63
|
*/
|
|
26
64
|
export function diagnosticsResult(
|
|
27
65
|
command: string,
|
|
28
66
|
diagnostics: Diagnostic[],
|
|
67
|
+
nextSteps?: KernelNextStep[],
|
|
29
68
|
): KernelCommandResult<CheckResult> {
|
|
30
69
|
const summary = {
|
|
31
70
|
error: diagnostics.filter((d) => d.severity === "error").length,
|
|
@@ -34,10 +73,14 @@ export function diagnosticsResult(
|
|
|
34
73
|
};
|
|
35
74
|
const status: CheckResult["status"] =
|
|
36
75
|
summary.error > 0 ? "fail" : summary.warning > 0 ? "warn" : "pass";
|
|
76
|
+
const resolvedNextSteps =
|
|
77
|
+
nextSteps ?? (summary.error > 0 ? defaultFailNextSteps(command) : undefined);
|
|
78
|
+
const counts = formatCounts(summary.error, summary.warning);
|
|
37
79
|
return {
|
|
38
80
|
data: { command, status, diagnostics, summary },
|
|
39
81
|
exitCode: summary.error > 0 ? 1 : 0,
|
|
40
|
-
summary:
|
|
82
|
+
summary: counts ? `[${command}] ${counts}` : `[${command}]`,
|
|
83
|
+
nextSteps: resolvedNextSteps,
|
|
41
84
|
};
|
|
42
85
|
}
|
|
43
86
|
|
|
@@ -58,11 +101,16 @@ function emptySummary(): CheckResult["summary"] {
|
|
|
58
101
|
* Canonical success result. RFC-0203: emits an empty `CheckResult` (no
|
|
59
102
|
* diagnostics) rather than the legacy `{ command, status, violations }` shape.
|
|
60
103
|
*/
|
|
61
|
-
export function passResult(
|
|
104
|
+
export function passResult(
|
|
105
|
+
command: string,
|
|
106
|
+
summary?: string,
|
|
107
|
+
nextSteps?: KernelNextStep[],
|
|
108
|
+
): KernelCommandResult<CheckResult> {
|
|
62
109
|
return {
|
|
63
110
|
data: { command, status: "pass", diagnostics: [], summary: emptySummary() },
|
|
64
111
|
exitCode: 0,
|
|
65
|
-
summary: summary ??
|
|
112
|
+
summary: summary ?? `[${command}] OK`,
|
|
113
|
+
nextSteps,
|
|
66
114
|
};
|
|
67
115
|
}
|
|
68
116
|
|
|
@@ -70,10 +118,14 @@ export function passResult(command: string, summary?: string): KernelCommandResu
|
|
|
70
118
|
* Canonical failure result — always exits 1 (preserving the historical
|
|
71
119
|
* failResult contract). RFC-0203: violation strings become error Diagnostics
|
|
72
120
|
* keyed by the command name.
|
|
121
|
+
*
|
|
122
|
+
* When `nextSteps` are not provided, a default agent-actionable nextStep is
|
|
123
|
+
* emitted: "Fix the error diagnostics … then re-run the pipeline".
|
|
73
124
|
*/
|
|
74
125
|
export function failResult(
|
|
75
126
|
command: string,
|
|
76
127
|
violations: string[],
|
|
128
|
+
nextSteps?: KernelNextStep[],
|
|
77
129
|
): KernelCommandResult<CheckResult> {
|
|
78
130
|
const diagnostics = violationsToDiagnostics(command, violations);
|
|
79
131
|
return {
|
|
@@ -84,7 +136,8 @@ export function failResult(
|
|
|
84
136
|
summary: { error: diagnostics.length, warning: 0, info: 0 },
|
|
85
137
|
},
|
|
86
138
|
exitCode: 1,
|
|
87
|
-
summary:
|
|
139
|
+
summary: `[${command}] ${violations.length} violation${violations.length === 1 ? "" : "s"}`,
|
|
140
|
+
nextSteps: nextSteps ?? defaultFailNextSteps(command),
|
|
88
141
|
};
|
|
89
142
|
}
|
|
90
143
|
|
|
@@ -97,6 +150,7 @@ export function failResult(
|
|
|
97
150
|
export function resultFromViolations(
|
|
98
151
|
command: string,
|
|
99
152
|
violations: string[],
|
|
153
|
+
nextSteps?: KernelNextStep[],
|
|
100
154
|
): KernelCommandResult<CheckResult> {
|
|
101
|
-
return violations.length > 0 ? failResult(command, violations) : passResult(command);
|
|
155
|
+
return violations.length > 0 ? failResult(command, violations, nextSteps) : passResult(command);
|
|
102
156
|
}
|
|
@@ -17,7 +17,32 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
17
17
|
import { join } from "node:path";
|
|
18
18
|
import { z } from "zod";
|
|
19
19
|
import { parse as parseYaml } from "yaml";
|
|
20
|
-
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Minimal Finding shape matching @syrokomskyi/axiom-study's Finding interface.
|
|
23
|
+
* Defined locally so the package typechecks without axiom-study installed.
|
|
24
|
+
* See share/types/axiom-study.d.ts for the full canonical declaration.
|
|
25
|
+
*/
|
|
26
|
+
interface Finding {
|
|
27
|
+
findingId: string;
|
|
28
|
+
semanticFingerprint: { algorithm: "sha256"; digest: string; size: number; mediaType: string };
|
|
29
|
+
methodologyId: string;
|
|
30
|
+
ruleId: string;
|
|
31
|
+
affectedSubjectId: string;
|
|
32
|
+
title: string;
|
|
33
|
+
severity: "info" | "low" | "medium" | "high" | "critical";
|
|
34
|
+
evidence: Array<{
|
|
35
|
+
evidenceRef: {
|
|
36
|
+
artifactId: string;
|
|
37
|
+
rootDigest: { algorithm: "sha256"; digest: string; size: number; mediaType: string };
|
|
38
|
+
schema: string;
|
|
39
|
+
};
|
|
40
|
+
selector: string;
|
|
41
|
+
evidenceClass: string;
|
|
42
|
+
}>;
|
|
43
|
+
uncertainty: unknown[];
|
|
44
|
+
extension: Record<string, unknown>;
|
|
45
|
+
}
|
|
21
46
|
|
|
22
47
|
export const suppressionRuleSchema = z.object({
|
|
23
48
|
ruleId: z.string().min(1),
|
|
@@ -9,6 +9,7 @@ Stack-agnostic utility used by both engine and site plugin (RFC-0868).</purpose>
|
|
|
9
9
|
</MODULE_CONTRACT>
|
|
10
10
|
<CHANGE_SUMMARY>
|
|
11
11
|
<item>RFC-0868: extracted from werkstatt-site/src/content/system-manifest.ts.</item>
|
|
12
|
+
<item>RFC-0911: add seo?.anchorText?.extraStopPhrases for anchor-text stop-list extension.</item>
|
|
12
13
|
</CHANGE_SUMMARY>
|
|
13
14
|
*/
|
|
14
15
|
|
|
@@ -101,6 +102,13 @@ export interface SystemManifest {
|
|
|
101
102
|
swapOrder?: boolean;
|
|
102
103
|
};
|
|
103
104
|
};
|
|
105
|
+
/** RFC-0911: SEO validator configuration extension point. */
|
|
106
|
+
seo?: {
|
|
107
|
+
anchorText?: {
|
|
108
|
+
/** Added to the built-in de/uk stop-list. */
|
|
109
|
+
extraStopPhrases?: Record<string, string[]>;
|
|
110
|
+
};
|
|
111
|
+
};
|
|
104
112
|
}
|
|
105
113
|
|
|
106
114
|
export interface SystemManifestLoadResult {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "1.0.0",
|
|
3
|
-
"totalCount":
|
|
3
|
+
"totalCount": 59,
|
|
4
4
|
"entries": [
|
|
5
5
|
{
|
|
6
6
|
"id": "approach",
|
|
@@ -218,6 +218,18 @@
|
|
|
218
218
|
"sourceFile": "packages/werkstatt-site/src/domain/ontology/archetypes/components/footer-promo.yaml",
|
|
219
219
|
"layer": "component"
|
|
220
220
|
},
|
|
221
|
+
{
|
|
222
|
+
"id": "gratitude",
|
|
223
|
+
"displayName": "Gratitude",
|
|
224
|
+
"semanticRole": "client-gratitude",
|
|
225
|
+
"version": "1.0.0",
|
|
226
|
+
"layoutHint": "card-grid",
|
|
227
|
+
"acceptedCosmicNames": [
|
|
228
|
+
"Gonggong"
|
|
229
|
+
],
|
|
230
|
+
"sourceFile": "packages/werkstatt-site/src/domain/ontology/archetypes/sections/gratitude.yaml",
|
|
231
|
+
"layer": "section"
|
|
232
|
+
},
|
|
221
233
|
{
|
|
222
234
|
"id": "hero",
|
|
223
235
|
"displayName": "Hero",
|
|
@@ -704,6 +716,7 @@
|
|
|
704
716
|
"article-index",
|
|
705
717
|
"audience-self-identification",
|
|
706
718
|
"breadcrumbs",
|
|
719
|
+
"client-gratitude",
|
|
707
720
|
"client-ownership-disclosure",
|
|
708
721
|
"consent-gated-chat-launcher",
|
|
709
722
|
"credits-gallery",
|
|
@@ -777,6 +790,7 @@
|
|
|
777
790
|
"Elara": "@warpgogol/werkstatt-site/ui/sections/dynamic-status-block",
|
|
778
791
|
"Atlas": "@warpgogol/werkstatt-site/ui/sections/faq-list",
|
|
779
792
|
"Dione": "@warpgogol/werkstatt-site/ui/sections/final-cta",
|
|
793
|
+
"Gonggong": "@warpgogol/werkstatt-site/ui/sections/gratitude",
|
|
780
794
|
"Europa": "@warpgogol/werkstatt-site/ui/sections/hero",
|
|
781
795
|
"Phobos": "@warpgogol/werkstatt-site/ui/sections/hero-decision-card",
|
|
782
796
|
"Ganymede": "@warpgogol/werkstatt-site/ui/sections/impact",
|
|
@@ -854,6 +868,7 @@
|
|
|
854
868
|
"dynamic-status-block": "Elara",
|
|
855
869
|
"faq-list": "Atlas",
|
|
856
870
|
"final-cta": "Dione",
|
|
871
|
+
"gratitude": "Gonggong",
|
|
857
872
|
"hero": "Europa",
|
|
858
873
|
"hero-decision-card": "Phobos",
|
|
859
874
|
"impact": "Ganymede",
|
|
@@ -935,6 +950,7 @@
|
|
|
935
950
|
"Elara": "data-driven-status-indicator",
|
|
936
951
|
"Atlas": "approach",
|
|
937
952
|
"Dione": "final-cta",
|
|
953
|
+
"Gonggong": "client-gratitude",
|
|
938
954
|
"Europa": "hero",
|
|
939
955
|
"Phobos": "hero",
|
|
940
956
|
"Ganymede": "impact",
|
|
@@ -66,14 +66,12 @@ const visibilityExprSchema: z.ZodType<unknown> = z.lazy(() =>
|
|
|
66
66
|
export const BlockEntrySchema = z
|
|
67
67
|
.object({
|
|
68
68
|
/**
|
|
69
|
-
*
|
|
70
|
-
* block-level attribution. Must be kebab-case
|
|
71
|
-
* page.block.validate enforces uniqueness within a page.
|
|
69
|
+
* Mandatory stable block identifier for analytics, anchor links, and
|
|
70
|
+
* block-level attribution. Must be kebab-case. RFC-0914 makes this required.
|
|
71
|
+
* page.block.validate enforces uniqueness within a page (B-05).
|
|
72
|
+
* page.blocks.extract.validate enforces presence and format (BLOCK-ID-MISSING, BLOCK-ID-INVALID).
|
|
72
73
|
*/
|
|
73
|
-
id: z
|
|
74
|
-
.string()
|
|
75
|
-
.regex(/^[a-z0-9-]+$/, "Block id must be kebab-case")
|
|
76
|
-
.optional(),
|
|
74
|
+
id: z.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "Block id must be kebab-case"),
|
|
77
75
|
|
|
78
76
|
/**
|
|
79
77
|
* Cosmic name identifying the section/component for this block.
|
|
@@ -17,6 +17,7 @@ import { systemSharedContextSchema, systemGrowthSchema } from "./growth.ts";
|
|
|
17
17
|
import { systemReleaseSchema } from "./release.ts";
|
|
18
18
|
import { systemTextSchema } from "./text.ts";
|
|
19
19
|
import { systemIntegrationsSchema } from "./integrations.ts";
|
|
20
|
+
import { systemVerificationSchema } from "./verification.ts";
|
|
20
21
|
import { semanticPageTypeSchema, articleMetadataSchema, pageOutputSchema } from "./page-output.ts";
|
|
21
22
|
|
|
22
23
|
/**
|
|
@@ -482,6 +483,13 @@ export const systemManifestSchema = z.object({
|
|
|
482
483
|
.optional(),
|
|
483
484
|
})
|
|
484
485
|
.optional(),
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Search engine verification config (RFC-0909).
|
|
489
|
+
* Declares per-engine verification method and token.
|
|
490
|
+
* When absent, search.verification.validate emits SEARCH-VERIFY-01.
|
|
491
|
+
*/
|
|
492
|
+
verification: systemVerificationSchema.optional(),
|
|
485
493
|
});
|
|
486
494
|
|
|
487
495
|
export type SystemManifest = z.infer<typeof systemManifestSchema>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Verification block schema (RFC-0909) for the system manifest.
|
|
4
|
+
Declares per-search-engine verification method and token for Google Search Console.</purpose>
|
|
5
|
+
<non-goals>
|
|
6
|
+
<item>Do not introduce app-specific runtime composition or deployment behavior into this reusable package source file.</item>
|
|
7
|
+
</non-goals>
|
|
8
|
+
</MODULE_CONTRACT>
|
|
9
|
+
<CHANGE_SUMMARY>
|
|
10
|
+
<item>RFC-0909: initial verification schema for search engine verification surface.</item>
|
|
11
|
+
</CHANGE_SUMMARY>
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The `verification:` block in src/content/system.md. Declares per-search-engine
|
|
18
|
+
* verification method and token so that search.verification.validate can enforce
|
|
19
|
+
* the declaration and the layout can emit the google-site-verification meta tag.
|
|
20
|
+
*
|
|
21
|
+
* Example src/content/system.md verification block:
|
|
22
|
+
* verification:
|
|
23
|
+
* google:
|
|
24
|
+
* method: dns-txt
|
|
25
|
+
* token: google-site-verification=abc123...
|
|
26
|
+
*/
|
|
27
|
+
export const systemVerificationSchema = z.object({
|
|
28
|
+
google: z
|
|
29
|
+
.object({
|
|
30
|
+
method: z.enum(["dns-txt", "meta-tag"]),
|
|
31
|
+
/**
|
|
32
|
+
* dns-txt: full TXT value "google-site-verification=...".
|
|
33
|
+
* meta-tag: content attribute of the google-site-verification meta tag.
|
|
34
|
+
*/
|
|
35
|
+
token: z.string().min(1),
|
|
36
|
+
})
|
|
37
|
+
.optional(),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export type SystemVerification = z.infer<typeof systemVerificationSchema>;
|
package/src/passport/sign.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* <MODULE_CONTRACT>
|
|
2
|
-
<purpose>Facilitates Ed25519 key generation, signing, and verification for verifiable credentials.</purpose>
|
|
2
|
+
<purpose>Facilitates Ed25519 key generation, signing, and verification for verifiable credentials via shared signing core (RFC-0921).</purpose>
|
|
3
3
|
<non-goals>
|
|
4
4
|
<item>Do not handle raw key storage or management.</item>
|
|
5
5
|
<item>Do not perform network operations or external API calls.</item>
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
</MODULE_CONTRACT>
|
|
9
9
|
<CHANGE_SUMMARY>
|
|
10
10
|
<item>Tidied by compass.changesummary.tidy; see git history for prior entries.</item>
|
|
11
|
+
<item>RFC-0921: delegate Ed25519 sign/verify/keygen to shared signing core (@warpgogol/werkstatt/signing). Remove @noble/ed25519 import.</item>
|
|
11
12
|
</CHANGE_SUMMARY> */
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -24,8 +25,14 @@
|
|
|
24
25
|
* - This module never logs, writes, or returns private key material.
|
|
25
26
|
*/
|
|
26
27
|
|
|
27
|
-
import * as ed from "@noble/ed25519";
|
|
28
28
|
import bs58 from "bs58";
|
|
29
|
+
import {
|
|
30
|
+
generateKeyPair as signingGenerateKeyPair,
|
|
31
|
+
signBytes as signingSignBytes,
|
|
32
|
+
verifyBytes as signingVerifyBytes,
|
|
33
|
+
fromHex,
|
|
34
|
+
toHex,
|
|
35
|
+
} from "@warpgogol/werkstatt/signing";
|
|
29
36
|
import type { VCProof, VerifiableCredential } from "./schema.ts";
|
|
30
37
|
|
|
31
38
|
// ---------------------------------------------------------------------------
|
|
@@ -90,12 +97,11 @@ export async function generateKeypair(): Promise<{
|
|
|
90
97
|
publicKeyBytes: Uint8Array;
|
|
91
98
|
publicKeyMultibase: string;
|
|
92
99
|
}> {
|
|
93
|
-
const
|
|
94
|
-
const publicKeyBytes = await ed.getPublicKeyAsync(privateKeyBytes);
|
|
100
|
+
const keyPair = await signingGenerateKeyPair();
|
|
95
101
|
return {
|
|
96
|
-
privateKeyHex:
|
|
97
|
-
publicKeyBytes,
|
|
98
|
-
publicKeyMultibase: toMultibase(
|
|
102
|
+
privateKeyHex: toHex(keyPair.privateKey),
|
|
103
|
+
publicKeyBytes: keyPair.publicKey,
|
|
104
|
+
publicKeyMultibase: toMultibase(keyPair.publicKey),
|
|
99
105
|
};
|
|
100
106
|
}
|
|
101
107
|
|
|
@@ -110,7 +116,7 @@ export async function generateKeypair(): Promise<{
|
|
|
110
116
|
* @param privateKeyHex — 32-byte Ed25519 private key as hex (from env secret)
|
|
111
117
|
* @param verificationMethod — DID key reference (e.g. "did:web:example.org#key-v1")
|
|
112
118
|
*/
|
|
113
|
-
// @ai-invariant: signCredential uses
|
|
119
|
+
// @ai-invariant: signCredential uses the shared signing core (RFC-0921). Private keys arrive
|
|
114
120
|
// exclusively as hex env vars — never logged, written to disk, or returned.
|
|
115
121
|
// The signed output is a W3C VC with Ed25519Signature2020 proof.
|
|
116
122
|
|
|
@@ -119,9 +125,9 @@ export async function signCredential(
|
|
|
119
125
|
privateKeyHex: string,
|
|
120
126
|
verificationMethod: string,
|
|
121
127
|
): Promise<VCProof> {
|
|
122
|
-
const privateKeyBytes =
|
|
128
|
+
const privateKeyBytes = fromHex(privateKeyHex);
|
|
123
129
|
const message = credentialBytes(subject);
|
|
124
|
-
const signatureBytes = await
|
|
130
|
+
const signatureBytes = await signingSignBytes(privateKeyBytes, message);
|
|
125
131
|
|
|
126
132
|
return {
|
|
127
133
|
type: "Ed25519Signature2020",
|
|
@@ -153,7 +159,7 @@ export async function verifyCredential(
|
|
|
153
159
|
const publicKeyBytes = fromMultibase(publicKeyMultibase);
|
|
154
160
|
const signatureBytes = fromMultibase(proof.proofValue);
|
|
155
161
|
const message = credentialBytes(subject);
|
|
156
|
-
return await
|
|
162
|
+
return await signingVerifyBytes(publicKeyBytes, message, signatureBytes);
|
|
157
163
|
} catch {
|
|
158
164
|
return false;
|
|
159
165
|
}
|
|
@@ -173,8 +179,8 @@ export async function verifyCredential(
|
|
|
173
179
|
* @param message — canonical bytes to sign
|
|
174
180
|
*/
|
|
175
181
|
export async function signBytes(privateKeyHex: string, message: Uint8Array): Promise<string> {
|
|
176
|
-
const privateKeyBytes =
|
|
177
|
-
const signatureBytes = await
|
|
182
|
+
const privateKeyBytes = fromHex(privateKeyHex);
|
|
183
|
+
const signatureBytes = await signingSignBytes(privateKeyBytes, message);
|
|
178
184
|
return toMultibase(signatureBytes);
|
|
179
185
|
}
|
|
180
186
|
|
|
@@ -190,7 +196,7 @@ export async function verifyBytes(
|
|
|
190
196
|
try {
|
|
191
197
|
const publicKeyBytes = fromMultibase(publicKeyMultibase);
|
|
192
198
|
const signatureBytes = fromMultibase(signatureMultibase);
|
|
193
|
-
return await
|
|
199
|
+
return await signingVerifyBytes(publicKeyBytes, message, signatureBytes);
|
|
194
200
|
} catch {
|
|
195
201
|
return false;
|
|
196
202
|
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>
|
|
4
|
+
ARD (Agentic Resource Discovery) ai-catalog.json projection. Pure, dependency-free
|
|
5
|
+
formatter that projects a site's Agent Surface Manifest into the ai-catalog data model
|
|
6
|
+
for /.well-known/ai-catalog.json. No I/O — the kernel command loads the manifest and
|
|
7
|
+
passes it here.
|
|
8
|
+
</purpose>
|
|
9
|
+
<non-goals>
|
|
10
|
+
<item>Do not read files — callers load and pass the manifest.</item>
|
|
11
|
+
<item>Do not duplicate logic from api-catalog or mcp-card — this is a separate spec.</item>
|
|
12
|
+
</non-goals>
|
|
13
|
+
</MODULE_CONTRACT>
|
|
14
|
+
<CHANGE_SUMMARY>
|
|
15
|
+
<item>Initial ARD ai-catalog.json projection for isitagentready.com ARD check.</item>
|
|
16
|
+
</CHANGE_SUMMARY>
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { AgentSurfaceManifest } from "./manifest.ts";
|
|
20
|
+
|
|
21
|
+
/** ARD ai-catalog entry — one per discoverable resource. */
|
|
22
|
+
export interface ArdCatalogEntry {
|
|
23
|
+
identifier: string;
|
|
24
|
+
displayName: string;
|
|
25
|
+
type: string;
|
|
26
|
+
url: string;
|
|
27
|
+
representativeQueries: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** ARD ai-catalog host object. */
|
|
31
|
+
export interface ArdCatalogHost {
|
|
32
|
+
displayName: string;
|
|
33
|
+
identifier: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** ARD ai-catalog top-level document. */
|
|
37
|
+
export interface ArdCatalog {
|
|
38
|
+
specVersion: string;
|
|
39
|
+
host: ArdCatalogHost;
|
|
40
|
+
entries: ArdCatalogEntry[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Pure: project Agent Surface Manifest into ARD ai-catalog.json.
|
|
45
|
+
* Deterministic — entries are sorted by identifier for byte-identical output (DNA-58).
|
|
46
|
+
*/
|
|
47
|
+
export function buildArdCatalog(
|
|
48
|
+
manifest: AgentSurfaceManifest,
|
|
49
|
+
hostDisplayName?: string,
|
|
50
|
+
): ArdCatalog {
|
|
51
|
+
const domain = manifest.baseUrl.replace(/^https?:\/\//, "").replace(/\/+$/, "");
|
|
52
|
+
const displayName = hostDisplayName ?? manifest.site;
|
|
53
|
+
|
|
54
|
+
const entries: ArdCatalogEntry[] = [];
|
|
55
|
+
|
|
56
|
+
// MCP server card entry
|
|
57
|
+
if (manifest.interfaces.mcp) {
|
|
58
|
+
entries.push({
|
|
59
|
+
identifier: `urn:air:${domain}:server:mcp`,
|
|
60
|
+
displayName: "MCP Server",
|
|
61
|
+
type: "application/mcp-server-card+json",
|
|
62
|
+
url: "/.well-known/mcp/server-card.json",
|
|
63
|
+
representativeQueries: [
|
|
64
|
+
"What tools are available on this MCP server",
|
|
65
|
+
"List MCP server capabilities and endpoints",
|
|
66
|
+
],
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// A2A Agent Card entry
|
|
71
|
+
entries.push({
|
|
72
|
+
identifier: `urn:air:${domain}:agent:a2a`,
|
|
73
|
+
displayName: "A2A Agent Card",
|
|
74
|
+
type: "application/a2a+json",
|
|
75
|
+
url: "/.well-known/agent-card.json",
|
|
76
|
+
representativeQueries: [
|
|
77
|
+
"What agent capabilities does this site offer",
|
|
78
|
+
"Get the A2A agent card for this service",
|
|
79
|
+
],
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// OpenAPI entry
|
|
83
|
+
if (manifest.interfaces.openapi) {
|
|
84
|
+
entries.push({
|
|
85
|
+
identifier: `urn:air:${domain}:api:openapi`,
|
|
86
|
+
displayName: "OpenAPI Specification",
|
|
87
|
+
type: "application/vnd.oai.openapi+json",
|
|
88
|
+
url: manifest.interfaces.openapi,
|
|
89
|
+
representativeQueries: [
|
|
90
|
+
"What API endpoints are available on this site",
|
|
91
|
+
"Get the OpenAPI specification for agent integration",
|
|
92
|
+
],
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Agent Surface Manifest entry
|
|
97
|
+
entries.push({
|
|
98
|
+
identifier: `urn:air:${domain}:manifest:agent-surface`,
|
|
99
|
+
displayName: "Agent Surface Manifest",
|
|
100
|
+
type: "application/json",
|
|
101
|
+
url: "/.well-known/agent.json",
|
|
102
|
+
representativeQueries: [
|
|
103
|
+
"What agent discovery endpoints does this site provide",
|
|
104
|
+
"Get the agent surface manifest for this site",
|
|
105
|
+
],
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Knowledge domain entries
|
|
109
|
+
for (const ref of manifest.knowledge) {
|
|
110
|
+
entries.push({
|
|
111
|
+
identifier: `urn:air:${domain}:knowledge:${ref.domain}`,
|
|
112
|
+
displayName: `${ref.domain.charAt(0).toUpperCase() + ref.domain.slice(1)} Knowledge`,
|
|
113
|
+
type: "application/json",
|
|
114
|
+
url: ref.url,
|
|
115
|
+
representativeQueries: [
|
|
116
|
+
`What ${ref.domain} information is available on this site`,
|
|
117
|
+
`Get structured ${ref.domain} data for agent consumption`,
|
|
118
|
+
],
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Sort entries by identifier for deterministic output
|
|
123
|
+
entries.sort((a, b) => a.identifier.localeCompare(b.identifier));
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
specVersion: "1.0",
|
|
127
|
+
host: {
|
|
128
|
+
displayName,
|
|
129
|
+
identifier: `did:web:${domain}`,
|
|
130
|
+
},
|
|
131
|
+
entries,
|
|
132
|
+
};
|
|
133
|
+
}
|
package/src/share/agent/index.ts
CHANGED