From 471c0b1c4100d95e3e9b7d49b21c99a6872630db Mon Sep 17 00:00:00 2001 From: Gleb Galkin Date: Sun, 26 Apr 2026 14:54:04 +0200 Subject: [PATCH 1/2] fix(v2): schema 404, CI cleanup, website link in README - website/public/schema/kndl-memory.schema.json: add missing schema file (was returning 404 on kndl.artdaw.com/schema/kndl-memory.schema.json) - main.tsx: router redirects for schema paths so GitHub Pages SPA intercept does not show 'Unexpected Application Error' before the file is deployed - README.md: website badge and prominent link to kndl.artdaw.com - ci: remove Python CodeQL job (Python implementation deleted in v2) - ci: bump Node.js to 24 in deploy and test workflows --- .github/workflows/codeql.yml | 16 ---- .github/workflows/deploy-website.yml | 2 +- .github/workflows/kndl-workflow.yml | 2 +- README.md | 4 +- website/public/schema/kndl-memory.schema.json | 39 +++++++++ website/src/main.tsx | 6 ++ website/src/pages/ProtocolPage.tsx | 86 ++++++++++++++++++- 7 files changed, 135 insertions(+), 20 deletions(-) create mode 100644 website/public/schema/kndl-memory.schema.json diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index a2678a8..5e75e07 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -16,22 +16,6 @@ env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: - analyze-python: - name: Analyze Python - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v4 - with: - languages: python - build-mode: none # Python is interpreted; no build needed - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 - with: - category: "/language:python" analyze-typescript: name: Analyze TypeScript runs-on: ubuntu-latest diff --git a/.github/workflows/deploy-website.yml b/.github/workflows/deploy-website.yml index 9932679..651b7a8 100644 --- a/.github/workflows/deploy-website.yml +++ b/.github/workflows/deploy-website.yml @@ -30,7 +30,7 @@ jobs: - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 cache: pnpm cache-dependency-path: website/pnpm-lock.yaml diff --git a/.github/workflows/kndl-workflow.yml b/.github/workflows/kndl-workflow.yml index 31314b6..6ddd012 100644 --- a/.github/workflows/kndl-workflow.yml +++ b/.github/workflows/kndl-workflow.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "24" cache: "npm" cache-dependency-path: packages/kndl-memory/package-lock.json - name: Install dependencies diff --git a/README.md b/README.md index fa2ffff..02dc9f6 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,9 @@ [![CI](https://github.com/artdaw/KNDL/actions/workflows/kndl-workflow.yml/badge.svg)](https://github.com/artdaw/KNDL/actions/workflows/kndl-workflow.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-00e5a0.svg)](LICENSE) [![TypeScript](https://img.shields.io/badge/TypeScript-5.4-3178C6.svg)](packages/kndl-memory) -[![Docs](https://img.shields.io/badge/Docs-kndl.artdaw.com-00e5a0.svg)](https://kndl.artdaw.com) +[![Website](https://img.shields.io/badge/Website-kndl.artdaw.com-00e5a0?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTEyIDJhMTAgMTAgMCAxIDAgMCAyMEExMCAxMCAwIDAgMCAxMiAyek04IDEyYTQgNCAwIDEgMSA4IDAgNCA0IDAgMCAxLTggMHoiLz48L3N2Zz4=)](https://kndl.artdaw.com) + +### **[→ kndl.artdaw.com](https://kndl.artdaw.com)** — live docs, protocol reference, interactive examples, explorer diff --git a/website/public/schema/kndl-memory.schema.json b/website/public/schema/kndl-memory.schema.json new file mode 100644 index 0000000..1acc3cf --- /dev/null +++ b/website/public/schema/kndl-memory.schema.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://kndl.artdaw.com/schema/fact.schema.json", + "title": "KNDL Fact", + "description": "One immutable assertion in the KNDL memory protocol. Every fact carries confidence, decay, provenance, and bitemporal validity. Facts are stored as individual JSON-LD files and never edited — updates are made via supersession.", + "type": "object", + "required": ["@id", "@type", "statement", "confidence", "source", "validFrom", "recordedAt"], + "additionalProperties": true, + "properties": { + "@context": { "type": ["string", "object"], "description": "JSON-LD context. Recommended: https://kndl.artdaw.com/context/v1.jsonld" }, + "@id": { "type": "string", "minLength": 1, "description": "Globally unique fact identifier. Recommended pattern: fact:---" }, + "@type": { "type": "string", "description": "Usually 'Fact'. Use 'Action' for intents/rules." }, + + "statement": { "type": "string", "minLength": 1, "description": "Plain-language one-sentence assertion" }, + "subject": { "type": "string", "description": "Entity URI (for structured triple queries, e.g. customer:9281)" }, + "predicate": { "type": "string", "description": "Property name (for structured triple queries, e.g. creditScore)" }, + "object": { "description": "Value or object URI" }, + + "confidence": { "type": "number", "minimum": 0, "maximum": 1, "description": "Epistemic certainty 0.0–1.0. Reserve 1.0 for axiomatic facts only." }, + "decay": { "type": "string", "pattern": "^[0-9.]+/[0-9.]+(?:ns|us|ms|s|m|h|d|w|mo|y)$", "description": "Half-life decay spec: rate/window, e.g. '0.5/30d' halves confidence every 30 days." }, + + "source": { "type": "string", "description": "URI of asserting entity. Use human:// for user input, doi: for publications." }, + "validFrom": { "type": "string", "format": "date-time", "description": "ISO datetime — when the fact became true in the world" }, + "validUntil": { "type": "string", "format": "date-time", "description": "ISO datetime — explicit expiry (omit for open-ended)" }, + "observedAt": { "type": "string", "format": "date-time", "description": "ISO datetime — when an agent or sensor directly observed it (vs. heard about it)" }, + "recordedAt": { "type": "string", "format": "date-time", "description": "ISO datetime — when it entered memory (auto-set by the store)" }, + + "supersedes": { "type": "string", "description": "@id of the older fact this assertion replaces. The old fact is hidden from active queries but preserved for as-of time-travel." }, + "derivedFrom": { "type": "array", "items": { "type": "string" }, "description": "@ids of source facts this was inferred from" }, + "inference": { "type": "string", "description": "@id of the rule that produced this derived fact" }, + "negated": { "type": "boolean", "description": "true = this fact is known-false (positive negation under open-world assumption). Never substitute for 'unknown'." }, + + "classification": { "type": "string", "enum": ["PII", "PHI", "PCI", "CONFIDENTIAL", "INTERNAL", "PUBLIC"], "description": "Sensitivity label. PHI facts are filtered by default; require allowPhi:true and consent." }, + "consent": { "type": "string", "description": "@id of the consent scope covering PHI access" }, + "retention": { "type": "string", "description": "ISO duration or date for scheduled deletion, e.g. P7Y" }, + "tenant": { "type": "string", "description": "Opaque multi-tenant isolation key" }, + "tags": { "type": "array", "items": { "type": "string" }, "description": "Free-form labels for search and grouping" } + } +} diff --git a/website/src/main.tsx b/website/src/main.tsx index 1e55c31..c7767aa 100644 --- a/website/src/main.tsx +++ b/website/src/main.tsx @@ -38,6 +38,12 @@ const router = createBrowserRouter([ { path: "spec", element: }, { path: "spec/full", element: }, { path: "workflow", element: }, + // Schema file aliases — redirect to the canonical static file so the + // browser fetches the raw JSON rather than showing a router error. + // (Static files in public/ take precedence once deployed; these + // redirects are a fallback for the GitHub Pages SPA 404 intercept.) + { path: "schema/kndl-memory.schema.json", element: }, + { path: "schema/kndl-context.jsonld", element: }, ], }, ]); diff --git a/website/src/pages/ProtocolPage.tsx b/website/src/pages/ProtocolPage.tsx index 8ebafc5..690a42b 100644 --- a/website/src/pages/ProtocolPage.tsx +++ b/website/src/pages/ProtocolPage.tsx @@ -1,6 +1,90 @@ import { SEO, techArticleSchema } from "../components/SEO"; import styles from "./ProtocolPage.module.css"; +// ── JSON syntax highlighter ─────────────────────────────────────────────────── +// Single-pass tokenizer — no external library. + +type Token = { type: string; value: string }; + +function tokenizeJson(src: string): Token[] { + const tokens: Token[] = []; + let i = 0; + while (i < src.length) { + // Whitespace / structure + if (/[\s,[\]{}]/.test(src[i])) { + let v = ""; + while (i < src.length && /[\s,[\]{}]/.test(src[i])) v += src[i++]; + tokens.push({ type: "punct", value: v }); + continue; + } + // Colon + if (src[i] === ":") { + tokens.push({ type: "punct", value: src[i++] }); + continue; + } + // String + if (src[i] === '"') { + let v = '"'; + i++; + while (i < src.length && src[i] !== '"') { + if (src[i] === "\\") { v += src[i++]; } + v += src[i++]; + } + v += '"'; + i++; + // Determine if it's a key (next non-space char is ":") + let j = i; + while (j < src.length && src[j] === " ") j++; + const isKey = src[j] === ":"; + const raw = v.slice(1, -1); + if (isKey) { + const type = raw.startsWith("@") ? "at-key" : "key"; + tokens.push({ type, value: v }); + } else { + // URL vs plain string value + const type = raw.startsWith("http") || raw.startsWith("fact:") || raw.startsWith("human://") || raw.startsWith("sensor://") ? "url" : "string"; + tokens.push({ type, value: v }); + } + continue; + } + // Number + if (/[-\d]/.test(src[i])) { + let v = ""; + while (i < src.length && /[-\d.eE+]/.test(src[i])) v += src[i++]; + tokens.push({ type: "number", value: v }); + continue; + } + // true / false / null + if (src.startsWith("true", i)) { tokens.push({ type: "bool", value: "true" }); i += 4; continue; } + if (src.startsWith("false", i)) { tokens.push({ type: "bool", value: "false" }); i += 5; continue; } + if (src.startsWith("null", i)) { tokens.push({ type: "null", value: "null" }); i += 4; continue; } + tokens.push({ type: "punct", value: src[i++] }); + } + return tokens; +} + +const TOKEN_COLORS: Record = { + "at-key": "var(--accent)", + "key": "var(--accent2)", + "string": "var(--accent4)", + "url": "#7dd3fc", + "number": "#f97316", + "bool": "var(--accent3)", + "null": "var(--text-dim)", + "punct": "var(--text-dim)", +}; + +function JsonHighlight({ src }: { src: string }) { + const tokens = tokenizeJson(src); + return ( +
+      {tokens.map((t, i) => (
+        {t.value}
+      ))}
+    
+ ); +} + const FACT_EXAMPLE = `{ "@id": "fact:customer-9281-creditscore-20260425T235249Z-b6c88774", "@type": "Fact", @@ -91,7 +175,7 @@ export default function ProtocolPage() {

fact-customer-9281-creditscore.fact.json
-
{FACT_EXAMPLE}
+
From e63c4d443070ef3bdf9ab973bd1e335fd3dd6d78 Mon Sep 17 00:00:00 2001 From: Gleb Galkin Date: Sun, 26 Apr 2026 14:56:41 +0200 Subject: [PATCH 2/2] chore: trigger CodeQL re-scan