q
File size: 5,742 Bytes
3365e13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// core/semantic.js — the SEMANTIC SKIN (gap C2): every κ-object the substrate produces carries an
// open-semantic-web type, so humans, agents, and W3C validators all read it the same way. Closes the
// weakest audit axis (κ names objects, but few declared a W3C @type). Two W3C standards, both native:
//   • JSON-LD / schema.org  — WHAT a thing is (a skill is a schema:HowTo; a file is a schema:DigitalDocument)
//   • PROV-O                — HOW it came to be (the κ-chain becomes prov:wasRevisionOf links)
// Pure, dependency-free, browser+node. The result round-trips through any JSON-LD/RDF tool.

export const HOLO_CONTEXT = {
  schema: "https://schema.org/",
  prov: "http://www.w3.org/ns/prov#",
  holo: "https://hologram.foundation/ns/",
  "@vocab": "https://schema.org/",
};

// schema.org type for a substrate object kind (the canonical mapping, L2 — fixed at the ingest boundary)
const TYPE_FOR = {
  skill: "HowTo",            // a reusable procedure = schema:HowTo (steps + when-to-use)
  file: "DigitalDocument",
  app: "SoftwareApplication",
  model: "SoftwareSourceCode",
  conversation: "Conversation",
  receipt: "CreativeWork",    // a sealed work record: schema:CreativeWork that is also a prov:Entity
};

// wrap any object as a typed, content-addressed JSON-LD node. `kappa` is its did:holo (the @id —
// content IS the identity, Law L1). `prov` is the κ-chain → prov:wasRevisionOf links (PROV-O).
export function asLinkedData({ kind, kappa, props = {}, prov = [] }) {
  const t = TYPE_FOR[kind] || "Thing";
  const node = {
    "@context": HOLO_CONTEXT,
    "@id": kappa,                                  // content-derived identity (no location — Law L1)
    "@type": Array.isArray(t) ? t : ["schema:" + t, "prov:Entity"],   // schema kind + PROV entity
    ...props,
  };
  if (prov.length >= 2) {                          // the version chain → PROV-O revision links
    const cur = prov[prov.length - 1], parent = prov[prov.length - 2];
    node["prov:wasRevisionOf"] = { "@id": parent.kappa };
    node["holo:version"] = cur.v;
  }
  node["prov:wasDerivedFrom"] = prov.length ? prov.map((p) => ({ "@id": p.kappa })) : undefined;
  return node;
}

// a skill → schema:HowTo (steps become schema:HowToStep), with its PROV-O revision chain.
export function skillAsHowTo({ name, description, instructions, kappa, prov = [] }) {
  const steps = String(instructions || "").split("\n").map((s) => s.trim()).filter(Boolean)
    .map((text, i) => ({ "@type": "schema:HowToStep", "schema:position": i + 1, "schema:text": text.replace(/^\d+[.)]\s*/, "") }));
  return asLinkedData({ kind: "skill", kappa, prov, props: { "schema:name": name, "schema:description": description, "schema:step": steps } });
}

// extension → IANA media type (schema:encodingFormat). Small, common set; default text/plain.
const MEDIA = { html: "text/html", htm: "text/html", js: "text/javascript", mjs: "text/javascript", json: "application/json", jsonld: "application/ld+json", css: "text/css", md: "text/markdown", txt: "text/plain", py: "text/x-python", svg: "image/svg+xml", wasm: "application/wasm", gz: "application/gzip" };
const mediaOf = (name) => MEDIA[String(name).toLowerCase().split(".").pop()] || "text/plain";

// a workspace file → schema:DigitalDocument (name + media type + byte size), content-addressed (L1).
export function fileAsDocument({ path, kappa, bytes = 0, mediaType, prov = [] }) {
  const name = String(path).split("/").filter(Boolean).pop() || String(path);
  return asLinkedData({ kind: "file", kappa, prov, props: {
    "schema:name": name, "schema:identifier": kappa,
    "schema:encodingFormat": mediaType || mediaOf(name), "schema:contentSize": bytes } });
}

// a built app → schema:SoftwareApplication (a self-contained WebGPU/Web app object).
export function appAsSoftware({ name, kappa, bytes = 0, prov = [] }) {
  return asLinkedData({ kind: "app", kappa, prov, props: {
    "schema:name": String(name).replace(/\.html$/i, ""), "schema:identifier": kappa,
    "schema:applicationCategory": "WebApplication", "schema:operatingSystem": "Any (WebGPU/Web)",
    "schema:fileSize": bytes } });
}

// a loaded model → schema:SoftwareSourceCode (the κ-object that runs on the GPU).
export function modelAsSource({ name, kappa, family, params, format, prov = [] }) {
  return asLinkedData({ kind: "model", kappa, prov, props: {
    "schema:name": name, "schema:identifier": kappa, "schema:programmingLanguage": "WGSL/WebGPU",
    "holo:family": family, "holo:parameters": params, "holo:format": format } });
}

// one dispatcher so any producer can type any κ-object: linkedDataFor("app", {...}) etc.
export function linkedDataFor(kind, props = {}) {
  switch (kind) {
    case "skill": return skillAsHowTo(props);
    case "file": return fileAsDocument(props);
    case "app": return appAsSoftware(props);
    case "model": return modelAsSource(props);
    default: return asLinkedData({ kind, kappa: props.kappa, prov: props.prov || [], props: props.props || props });
  }
}

// VERIFY (the gate): an object is semantically valid iff it has @context, an @id (κ), and a @type
// carrying both a schema.org kind and PROV-O lineage. Returns { ok, types, hasId, hasContext }.
export function verifySemantic(node) {
  const types = [].concat(node && node["@type"] || []);
  const hasSchema = types.some((t) => /^schema:|^https:\/\/schema\.org\//.test(t));
  const hasProv = types.some((t) => /^prov:/.test(t)) || !!node["prov:wasDerivedFrom"];
  const hasId = typeof node?.["@id"] === "string" && node["@id"].startsWith("did:holo:");
  const hasContext = !!node?.["@context"];
  return { ok: hasSchema && hasId && hasContext, hasSchema, hasProv, hasId, hasContext, types };
}