@konneal/engine 0.2.5 → 0.2.6
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 +2 -0
- package/dist/worker_public/src/index.js +37 -0
- package/docs/ADOPTION.md +61 -0
- package/package.json +1 -1
- package/workers/worker_public/src/index.ts +45 -0
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Konneal Engine
|
|
2
2
|
|
|
3
|
+
**New here?** Read [docs/ADOPTION.md](docs/ADOPTION.md) — adopt Konneal for your own standards and your own identity service.
|
|
4
|
+
|
|
3
5
|
The publisher-agnostic serving layer for standards intelligence: hybrid
|
|
4
6
|
retrieval over clause-structured corpora, citation-grounded answers,
|
|
5
7
|
typed tables/formulas/figures, conformance checking by execution, and
|
|
@@ -1585,9 +1585,46 @@ async function researchRoute(c) {
|
|
|
1585
1585
|
const session = c.env.SESSION_SECRET ? await sessionFrom(c.req, c.env) : null;
|
|
1586
1586
|
return handleResearch(c.env, c.ctx, c.req, session);
|
|
1587
1587
|
}
|
|
1588
|
+
function permissionsCatalog() {
|
|
1589
|
+
const groups = [];
|
|
1590
|
+
const perms = [];
|
|
1591
|
+
for (const d of P().datasets ?? []) {
|
|
1592
|
+
if (d.session && d.permission) {
|
|
1593
|
+
perms.push({
|
|
1594
|
+
id: "ai.dataset.externally-licensed",
|
|
1595
|
+
description: `Access the ${d.label} dataset (federated, externally licensed content)`
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
if (P().publisher.features?.drafts) {
|
|
1600
|
+
perms.push({ id: "ai.drafts", description: "Ask the assistant to prepare draft acts" });
|
|
1601
|
+
}
|
|
1602
|
+
perms.push({ id: "ai.memories", description: "Personalized memory files on the assistant" });
|
|
1603
|
+
perms.push({ id: "ai.research", description: "Deep-research multi-pass questions" });
|
|
1604
|
+
perms.push({ id: "ai.keys.admin", description: "Create and revoke API keys" });
|
|
1605
|
+
groups.push({ id: "ai", description: "The publications assistant's gated capabilities", permissions: perms });
|
|
1606
|
+
return {
|
|
1607
|
+
version: 1,
|
|
1608
|
+
verbs: ["read", "write", "admin"],
|
|
1609
|
+
groups
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1612
|
+
async function openapiCatalogRoute(c) {
|
|
1613
|
+
return json(
|
|
1614
|
+
{
|
|
1615
|
+
openapi: "3.1.0",
|
|
1616
|
+
info: { title: `${P().publisher.product_name} API`, version: c.env.INDEX_VERSION ?? "0" },
|
|
1617
|
+
paths: {},
|
|
1618
|
+
"x-oiml-permissions-catalog": permissionsCatalog()
|
|
1619
|
+
},
|
|
1620
|
+
200,
|
|
1621
|
+
corsHeaders(c.req)
|
|
1622
|
+
);
|
|
1623
|
+
}
|
|
1588
1624
|
var INFRA_ROUTES = [
|
|
1589
1625
|
{ method: "GET", pattern: "/", handler: serveIndexPage },
|
|
1590
1626
|
{ method: "GET", pattern: "/api/", handler: serveIndexPage },
|
|
1627
|
+
{ method: "GET", pattern: "/api/openapi.json", handler: openapiCatalogRoute },
|
|
1591
1628
|
{ method: "GET", pattern: "/index.html", handler: serveIndexPage },
|
|
1592
1629
|
{ method: "GET", pattern: "/assets/*", handler: unitAssetRoute },
|
|
1593
1630
|
{ method: "GET", pattern: "/docs/*", handler: docsRoute }
|
package/docs/ADOPTION.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Adopting Konneal: your own standards-intelligence site
|
|
2
|
+
|
|
3
|
+
Konneal is a publisher-agnostic engine: clause-cited, verifiable answers
|
|
4
|
+
over your own Metanorma documents and Primmel models — deployed on your
|
|
5
|
+
own infrastructure, with licensed third-party content (IEC, ISO and
|
|
6
|
+
peers) gated behind your identity service.
|
|
7
|
+
|
|
8
|
+
## Start
|
|
9
|
+
|
|
10
|
+
npm create @konneal/publisher my-sdo
|
|
11
|
+
|
|
12
|
+
The scaffolder interviews you for your publisher's facts — id, name,
|
|
13
|
+
domains, identity issuer, first dataset — and writes the whole
|
|
14
|
+
deployment skeleton: the worker, the site, the profile, the ingest
|
|
15
|
+
pipeline, CI. Every generated file is ordinary reviewable code.
|
|
16
|
+
|
|
17
|
+
Then the four declarations:
|
|
18
|
+
|
|
19
|
+
1. **Your profile** (`profile/*.yaml`): publisher identity, domains,
|
|
20
|
+
datasets, the corpora registry, the licensed-standards catalog,
|
|
21
|
+
prompt variables, smoke probes. Everything the serving surface knows
|
|
22
|
+
about you is this data, never engine code.
|
|
23
|
+
2. **Your corpus** (`sources/`): your Metanorma documents. The ingest
|
|
24
|
+
pipeline chunks along the documents' own clause boundaries, embeds,
|
|
25
|
+
and upserts into your indexes.
|
|
26
|
+
3. **Your models** (Primmel packages): the machine-readable layer —
|
|
27
|
+
requirements, constraints with threshold expressions, severity
|
|
28
|
+
condition sets — so answers can carry server-computed verdicts, not
|
|
29
|
+
paraphrases.
|
|
30
|
+
4. **Your identity**: any discovery-compliant OIDC provider. Register
|
|
31
|
+
the site (authorization code + PKCE), map roles to datasets and
|
|
32
|
+
groups to licensed keys in the provider's configuration — revoking a
|
|
33
|
+
role removes access on the next request, no deploy.
|
|
34
|
+
|
|
35
|
+
## Licensed content, gated
|
|
36
|
+
|
|
37
|
+
Licensed standards are fail-closed by construction: each licensed
|
|
38
|
+
package carries a license key; the deployment's profile declares the
|
|
39
|
+
whitelist; a request without an entitled scope never sees the licensed
|
|
40
|
+
text — it is filtered before the model reads anything. An anonymous
|
|
41
|
+
request cannot reach internal indexes at all: the public surface holds
|
|
42
|
+
no binding to them, and the internal worker re-verifies every session
|
|
43
|
+
itself.
|
|
44
|
+
|
|
45
|
+
## The proof
|
|
46
|
+
|
|
47
|
+
Your golden suite gates every promotion: content witnesses, refusal
|
|
48
|
+
legs, licensed pairs in both directions (entitled content serves;
|
|
49
|
+
unentitled never appears), context-utilization and verdict legs. The
|
|
50
|
+
nightly runs unattended against production and fails loudly on drift.
|
|
51
|
+
|
|
52
|
+
## Ask it from your terminal
|
|
53
|
+
|
|
54
|
+
The konneal CLI ships with the client package: npm i -g @konneal/client, then KONNEAL_BASE and KONNEAL_KEY in the environment give you konneal ask, konneal search and konneal keys list — streaming answers with citations and verdict blocks, the same API the site uses behind the same tiering.
|
|
55
|
+
|
|
56
|
+
## The path
|
|
57
|
+
|
|
58
|
+
`npm create @konneal/publisher` → declare the profile → ingest your
|
|
59
|
+
corpus → author one model package and one golden leg → map identity →
|
|
60
|
+
promote through the gate. Adoption is done when your team runs the
|
|
61
|
+
nightly and reads the gate without help.
|
package/package.json
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@oimlsmart/oiml-pubid": "^1.2.1"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {},
|
|
37
|
-
"version": "0.2.
|
|
37
|
+
"version": "0.2.6",
|
|
38
38
|
"description": "The Konneal engine: the publisher-agnostic build pipeline and API plane for standards intelligence (retrieval, answer contract, verdicts, evaluation).",
|
|
39
39
|
"license": "BSD-3-Clause",
|
|
40
40
|
"type": "module",
|
|
@@ -398,9 +398,54 @@ async function researchRoute(c: RouteContext): Promise<Response> {
|
|
|
398
398
|
// The non-API routes: the HTML pages, the unit-keyed figure assets and
|
|
399
399
|
// the rendered publication documents. Everything else is generated from
|
|
400
400
|
// the OpenAPI document.
|
|
401
|
+
|
|
402
|
+
// The service's own permission catalog (TODO.openapi/03): the identity
|
|
403
|
+
// service FETCHES this endpoint to verify every permission grant for
|
|
404
|
+
// this client — it never holds a copy. The catalog derives from the
|
|
405
|
+
// profile's own declarations (gated datasets, feature flags), so a
|
|
406
|
+
// deployment's catalog cannot drift from what it enforces.
|
|
407
|
+
function permissionsCatalog(): Record<string, unknown> {
|
|
408
|
+
const groups: Record<string, unknown>[] = [];
|
|
409
|
+
const perms: Record<string, unknown>[] = [];
|
|
410
|
+
for (const d of P().datasets ?? []) {
|
|
411
|
+
if (d.session && d.permission) {
|
|
412
|
+
perms.push({
|
|
413
|
+
id: "ai.dataset.externally-licensed",
|
|
414
|
+
description: `Access the ${d.label} dataset (federated, externally licensed content)`,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (P().publisher.features?.drafts) {
|
|
419
|
+
perms.push({ id: "ai.drafts", description: "Ask the assistant to prepare draft acts" });
|
|
420
|
+
}
|
|
421
|
+
perms.push({ id: "ai.memories", description: "Personalized memory files on the assistant" });
|
|
422
|
+
perms.push({ id: "ai.research", description: "Deep-research multi-pass questions" });
|
|
423
|
+
perms.push({ id: "ai.keys.admin", description: "Create and revoke API keys" });
|
|
424
|
+
groups.push({ id: "ai", description: "The publications assistant's gated capabilities", permissions: perms });
|
|
425
|
+
return {
|
|
426
|
+
version: 1,
|
|
427
|
+
verbs: ["read", "write", "admin"],
|
|
428
|
+
groups,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
async function openapiCatalogRoute(c: RouteContext): Promise<Response> {
|
|
433
|
+
return json(
|
|
434
|
+
{
|
|
435
|
+
openapi: "3.1.0",
|
|
436
|
+
info: { title: `${P().publisher.product_name} API`, version: c.env.INDEX_VERSION ?? "0" },
|
|
437
|
+
paths: {},
|
|
438
|
+
"x-oiml-permissions-catalog": permissionsCatalog(),
|
|
439
|
+
},
|
|
440
|
+
200,
|
|
441
|
+
corsHeaders(c.req),
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
|
|
401
445
|
const INFRA_ROUTES: Route[] = [
|
|
402
446
|
{ method: "GET", pattern: "/", handler: serveIndexPage },
|
|
403
447
|
{ method: "GET", pattern: "/api/", handler: serveIndexPage },
|
|
448
|
+
{ method: "GET", pattern: "/api/openapi.json", handler: openapiCatalogRoute },
|
|
404
449
|
{ method: "GET", pattern: "/index.html", handler: serveIndexPage },
|
|
405
450
|
{ method: "GET", pattern: "/assets/*", handler: unitAssetRoute },
|
|
406
451
|
{ method: "GET", pattern: "/docs/*", handler: docsRoute },
|