@kashscript/hudhud 0.1.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/LICENSE +133 -0
- package/README.md +67 -0
- package/dist/adapters/prisma.d.ts +170 -0
- package/dist/adapters/prisma.d.ts.map +1 -0
- package/dist/adapters/prisma.js +185 -0
- package/dist/adapters/prisma.js.map +1 -0
- package/dist/anchor/index.d.ts +55 -0
- package/dist/anchor/index.d.ts.map +1 -0
- package/dist/anchor/index.js +97 -0
- package/dist/anchor/index.js.map +1 -0
- package/dist/certificate/certificate.d.ts +115 -0
- package/dist/certificate/certificate.d.ts.map +1 -0
- package/dist/certificate/certificate.js +221 -0
- package/dist/certificate/certificate.js.map +1 -0
- package/dist/certificate/html.d.ts +10 -0
- package/dist/certificate/html.d.ts.map +1 -0
- package/dist/certificate/html.js +141 -0
- package/dist/certificate/html.js.map +1 -0
- package/dist/certificate/index.d.ts +3 -0
- package/dist/certificate/index.d.ts.map +1 -0
- package/dist/certificate/index.js +5 -0
- package/dist/certificate/index.js.map +1 -0
- package/dist/did-registry/index.d.ts +3 -0
- package/dist/did-registry/index.d.ts.map +1 -0
- package/dist/did-registry/index.js +10 -0
- package/dist/did-registry/index.js.map +1 -0
- package/dist/errors.d.ts +35 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +65 -0
- package/dist/errors.js.map +1 -0
- package/dist/exception/index.d.ts +97 -0
- package/dist/exception/index.d.ts.map +1 -0
- package/dist/exception/index.js +144 -0
- package/dist/exception/index.js.map +1 -0
- package/dist/identity/index.d.ts +33 -0
- package/dist/identity/index.d.ts.map +1 -0
- package/dist/identity/index.js +36 -0
- package/dist/identity/index.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/ledger/event-schema.d.ts +97 -0
- package/dist/ledger/event-schema.d.ts.map +1 -0
- package/dist/ledger/event-schema.js +54 -0
- package/dist/ledger/event-schema.js.map +1 -0
- package/dist/ledger/index.d.ts +7 -0
- package/dist/ledger/index.d.ts.map +1 -0
- package/dist/ledger/index.js +14 -0
- package/dist/ledger/index.js.map +1 -0
- package/dist/ledger/mutex.d.ts +9 -0
- package/dist/ledger/mutex.d.ts.map +1 -0
- package/dist/ledger/mutex.js +36 -0
- package/dist/ledger/mutex.js.map +1 -0
- package/dist/ledger/store.d.ts +31 -0
- package/dist/ledger/store.d.ts.map +1 -0
- package/dist/ledger/store.js +66 -0
- package/dist/ledger/store.js.map +1 -0
- package/dist/ledger/writer.d.ts +69 -0
- package/dist/ledger/writer.d.ts.map +1 -0
- package/dist/ledger/writer.js +167 -0
- package/dist/ledger/writer.js.map +1 -0
- package/dist/mcp/allowlist.d.ts +53 -0
- package/dist/mcp/allowlist.d.ts.map +1 -0
- package/dist/mcp/allowlist.js +65 -0
- package/dist/mcp/allowlist.js.map +1 -0
- package/dist/mcp/client.d.ts +68 -0
- package/dist/mcp/client.d.ts.map +1 -0
- package/dist/mcp/client.js +198 -0
- package/dist/mcp/client.js.map +1 -0
- package/dist/mcp/index.d.ts +3 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +5 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/privacy/index.d.ts +21 -0
- package/dist/privacy/index.d.ts.map +1 -0
- package/dist/privacy/index.js +96 -0
- package/dist/privacy/index.js.map +1 -0
- package/dist/runtime/dispatcher.d.ts +27 -0
- package/dist/runtime/dispatcher.d.ts.map +1 -0
- package/dist/runtime/dispatcher.js +66 -0
- package/dist/runtime/dispatcher.js.map +1 -0
- package/dist/runtime/escalation.d.ts +98 -0
- package/dist/runtime/escalation.d.ts.map +1 -0
- package/dist/runtime/escalation.js +188 -0
- package/dist/runtime/escalation.js.map +1 -0
- package/dist/runtime/index.d.ts +7 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +9 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/runner.d.ts +78 -0
- package/dist/runtime/runner.d.ts.map +1 -0
- package/dist/runtime/runner.js +145 -0
- package/dist/runtime/runner.js.map +1 -0
- package/dist/scope/index.d.ts +19 -0
- package/dist/scope/index.d.ts.map +1 -0
- package/dist/scope/index.js +44 -0
- package/dist/scope/index.js.map +1 -0
- package/dist/sop/compiler.d.ts +36 -0
- package/dist/sop/compiler.d.ts.map +1 -0
- package/dist/sop/compiler.js +54 -0
- package/dist/sop/compiler.js.map +1 -0
- package/dist/sop/index.d.ts +3 -0
- package/dist/sop/index.d.ts.map +1 -0
- package/dist/sop/index.js +5 -0
- package/dist/sop/index.js.map +1 -0
- package/dist/sop/service.d.ts +40 -0
- package/dist/sop/service.d.ts.map +1 -0
- package/dist/sop/service.js +140 -0
- package/dist/sop/service.js.map +1 -0
- package/package.json +112 -0
- package/src/adapters/prisma.ts +310 -0
- package/src/anchor/index.ts +149 -0
- package/src/certificate/certificate.ts +340 -0
- package/src/certificate/html.ts +156 -0
- package/src/certificate/index.ts +4 -0
- package/src/did-registry/index.ts +11 -0
- package/src/errors.ts +79 -0
- package/src/exception/index.ts +244 -0
- package/src/identity/index.ts +82 -0
- package/src/index.ts +33 -0
- package/src/ledger/event-schema.ts +61 -0
- package/src/ledger/index.ts +22 -0
- package/src/ledger/mutex.ts +37 -0
- package/src/ledger/store.ts +86 -0
- package/src/ledger/writer.ts +249 -0
- package/src/mcp/allowlist.ts +102 -0
- package/src/mcp/client.ts +284 -0
- package/src/mcp/index.ts +4 -0
- package/src/privacy/index.ts +123 -0
- package/src/runtime/dispatcher.ts +96 -0
- package/src/runtime/escalation.ts +292 -0
- package/src/runtime/index.ts +14 -0
- package/src/runtime/runner.ts +227 -0
- package/src/scope/index.ts +59 -0
- package/src/sop/compiler.ts +99 -0
- package/src/sop/index.ts +4 -0
- package/src/sop/service.ts +184 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// @kashscript/hudhud/anchor — external chain-tip anchoring + receipt store
|
|
3
|
+
// ----------------------------------------------------------------------------
|
|
4
|
+
// Periodic out-of-band publication of session chain tips: an attacker who
|
|
5
|
+
// rewrites the whole ledger consistently still can't rewrite an anchor
|
|
6
|
+
// published OUTSIDE the DB, so the auditor cross-checks the DB chain's latest
|
|
7
|
+
// hash against the anchor. Ships the interface + a merkle-root helper + an
|
|
8
|
+
// in-memory anchor and receipt store; the public witness (Rekor/OTS) is
|
|
9
|
+
// attest's `witness`, and the node-fs FileChainAnchor is a deployment adapter
|
|
10
|
+
// (kept out to keep hudhud edge-safe). Ported from the oreoasis-core-host engine.
|
|
11
|
+
// ============================================================================
|
|
12
|
+
import { sha256Hex } from "@kashscript/attest";
|
|
13
|
+
/** Deterministic SHA-256-over-sorted-tip-triples root. Simpler than a binary
|
|
14
|
+
* Merkle tree — the anchor's job is just to be unrewritable; the cross-check
|
|
15
|
+
* against the per-session chain hash is what proves tamper. */
|
|
16
|
+
export async function computeAnchorMerkleRoot(tips) {
|
|
17
|
+
const sorted = [...tips].sort((a, b) => (a.sessionId < b.sessionId ? -1 : 1));
|
|
18
|
+
const enc = new TextEncoder();
|
|
19
|
+
let acc = new Uint8Array(0);
|
|
20
|
+
// ⚠️ audit r1 F1 (KNOWN, deferred, wire-compatible with attest's witness anchor):
|
|
21
|
+
// this newline join is not injective. The injective fix is a wire-format change
|
|
22
|
+
// that would invalidate anchors a venture has already witnessed, so it lands
|
|
23
|
+
// behind a coordinated anchor-format v2 at the venture swap — see
|
|
24
|
+
// specs/hudhud-audit-r1.md. Kept byte-identical to attest for now.
|
|
25
|
+
for (const t of sorted) {
|
|
26
|
+
const segment = enc.encode(`${t.sessionId}\n${t.latestHash}\n${t.latestStepIndex}\n`);
|
|
27
|
+
const next = new Uint8Array(acc.length + segment.length);
|
|
28
|
+
next.set(acc);
|
|
29
|
+
next.set(segment, acc.length);
|
|
30
|
+
acc = next;
|
|
31
|
+
}
|
|
32
|
+
return sha256Hex(acc);
|
|
33
|
+
}
|
|
34
|
+
function frozenRecord(tips, merkleRoot, publishedAt) {
|
|
35
|
+
return Object.freeze({
|
|
36
|
+
publishedAt,
|
|
37
|
+
merkleRoot,
|
|
38
|
+
tipCount: tips.length,
|
|
39
|
+
tips: Object.freeze(tips.map((t) => Object.freeze({ ...t }))),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/** In-memory anchor — records publications for inspection. A legitimate
|
|
43
|
+
* dev/test backend (the anchor is then witnessed for the process lifetime). */
|
|
44
|
+
export class NullChainAnchor {
|
|
45
|
+
records = [];
|
|
46
|
+
async publish(tips) {
|
|
47
|
+
const record = frozenRecord(tips, await computeAnchorMerkleRoot(tips), new Date().toISOString());
|
|
48
|
+
this.records.push(record);
|
|
49
|
+
return record;
|
|
50
|
+
}
|
|
51
|
+
async history() {
|
|
52
|
+
return [...this.records];
|
|
53
|
+
}
|
|
54
|
+
async latest() {
|
|
55
|
+
return this.records.length === 0 ? null : this.records[this.records.length - 1];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
let lastIdMicros = 0;
|
|
59
|
+
function newReceiptId() {
|
|
60
|
+
let micros = Date.now() * 1000;
|
|
61
|
+
if (micros <= lastIdMicros)
|
|
62
|
+
micros = lastIdMicros + 1;
|
|
63
|
+
lastIdMicros = micros;
|
|
64
|
+
const rand = crypto.getRandomValues(new Uint8Array(6));
|
|
65
|
+
let hex = "";
|
|
66
|
+
for (const b of rand)
|
|
67
|
+
hex += b.toString(16).padStart(2, "0");
|
|
68
|
+
return `anchor_${micros.toString(36)}_${hex}`;
|
|
69
|
+
}
|
|
70
|
+
/** In-memory receipt store — the no-DB / test profile. */
|
|
71
|
+
export class InMemoryAnchorReceiptStore {
|
|
72
|
+
rows = [];
|
|
73
|
+
async append(record) {
|
|
74
|
+
const stored = Object.freeze({
|
|
75
|
+
...record,
|
|
76
|
+
tips: Object.freeze(record.tips.map((t) => Object.freeze({ ...t }))),
|
|
77
|
+
id: newReceiptId(),
|
|
78
|
+
createdAt: new Date().toISOString(),
|
|
79
|
+
});
|
|
80
|
+
this.rows.push(stored);
|
|
81
|
+
return stored;
|
|
82
|
+
}
|
|
83
|
+
async latest() {
|
|
84
|
+
return this.rows.length === 0 ? null : this.rows[this.rows.length - 1];
|
|
85
|
+
}
|
|
86
|
+
async history() {
|
|
87
|
+
return [...this.rows];
|
|
88
|
+
}
|
|
89
|
+
async latestForRoot(merkleRoot) {
|
|
90
|
+
for (let i = this.rows.length - 1; i >= 0; i--) {
|
|
91
|
+
if (this.rows[i].merkleRoot === merkleRoot)
|
|
92
|
+
return this.rows[i];
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/anchor/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,2EAA2E;AAC3E,+EAA+E;AAC/E,0EAA0E;AAC1E,uEAAuE;AACvE,8EAA8E;AAC9E,2EAA2E;AAC3E,wEAAwE;AACxE,8EAA8E;AAC9E,kFAAkF;AAClF,+EAA+E;AAE/E,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AA+B/C;;gEAEgE;AAChE,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,IAA6B;IACzE,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9E,MAAM,GAAG,GAAG,IAAI,WAAW,EAAE,CAAC;IAC9B,IAAI,GAAG,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC;IAC5B,kFAAkF;IAClF,gFAAgF;IAChF,6EAA6E;IAC7E,kEAAkE;IAClE,mEAAmE;IACnE,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,UAAU,KAAK,CAAC,CAAC,eAAe,IAAI,CAAC,CAAC;QACtF,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;QACzD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;QAC9B,GAAG,GAAG,IAAI,CAAC;IACb,CAAC;IACD,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC;AACxB,CAAC;AAED,SAAS,YAAY,CAAC,IAA6B,EAAE,UAAkB,EAAE,WAAmB;IAC1F,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,WAAW;QACX,UAAU;QACV,QAAQ,EAAE,IAAI,CAAC,MAAM;QACrB,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;KAC9D,CAAC,CAAC;AACL,CAAC;AAED;gFACgF;AAChF,MAAM,OAAO,eAAe;IACT,OAAO,GAAmB,EAAE,CAAC;IAE9C,KAAK,CAAC,OAAO,CAAC,IAA6B;QACzC,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,uBAAuB,CAAC,IAAI,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;QACjG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1B,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,KAAK,CAAC,OAAO;QACX,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,MAAM;QACV,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC;IACnF,CAAC;CACF;AAgBD,IAAI,YAAY,GAAG,CAAC,CAAC;AACrB,SAAS,YAAY;IACnB,IAAI,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;IAC/B,IAAI,MAAM,IAAI,YAAY;QAAE,MAAM,GAAG,YAAY,GAAG,CAAC,CAAC;IACtD,YAAY,GAAG,MAAM,CAAC;IACtB,MAAM,IAAI,GAAG,MAAM,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IACvD,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,MAAM,CAAC,IAAI,IAAI;QAAE,GAAG,IAAI,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC7D,OAAO,UAAU,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,GAAG,EAAE,CAAC;AAChD,CAAC;AAED,0DAA0D;AAC1D,MAAM,OAAO,0BAA0B;IACpB,IAAI,GAA0B,EAAE,CAAC;IAElD,KAAK,CAAC,MAAM,CAAC,MAAoB;QAC/B,MAAM,MAAM,GAAwB,MAAM,CAAC,MAAM,CAAC;YAChD,GAAG,MAAM;YACT,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;YACpE,EAAE,EAAE,YAAY,EAAE;YAClB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,CAAC,CAAC;QACH,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACvB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,KAAK,CAAC,MAAM;QACV,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC;IAC1E,CAAC;IAED,KAAK,CAAC,OAAO;QACX,OAAO,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,UAAkB;QACpC,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC/C,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC,UAAU,KAAK,UAAU;gBAAE,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC;QACpE,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { DsseEnvelope, DsseSigner } from "@kashscript/attest";
|
|
2
|
+
import type { AnchorRecord } from "../anchor";
|
|
3
|
+
import type { ExceptionClassificationRecord, FailureClass } from "../exception";
|
|
4
|
+
export declare const WORK_CERTIFICATE_FORMAT_VERSION: "kash-work-certificate/1";
|
|
5
|
+
export declare const WORK_CERTIFICATE_PAYLOAD_TYPE: "application/vnd.kash.work-certificate+json";
|
|
6
|
+
/** The AIUC-1 standard's canonical reference (web-verified). */
|
|
7
|
+
export declare const AIUC1_SOURCE: "https://www.aiuc-1.com/";
|
|
8
|
+
export interface Aiuc1Domain {
|
|
9
|
+
readonly code: "A" | "B" | "C" | "D" | "E" | "F";
|
|
10
|
+
readonly name: string;
|
|
11
|
+
readonly covers: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const AIUC1_DOMAINS: ReadonlyArray<Aiuc1Domain>;
|
|
14
|
+
export type WorkOutcomeStatus = "delivered_autonomous" | "delivered_with_signoff" | "declined";
|
|
15
|
+
export interface CertifiedWorkRecord {
|
|
16
|
+
readonly workItemId: string;
|
|
17
|
+
readonly sessionId: string;
|
|
18
|
+
readonly workflowId: string;
|
|
19
|
+
readonly workflowLabel?: string;
|
|
20
|
+
readonly status: WorkOutcomeStatus;
|
|
21
|
+
readonly slaDeadlineIso: string;
|
|
22
|
+
readonly completedAtIso: string;
|
|
23
|
+
readonly slaMet: boolean;
|
|
24
|
+
readonly ledgerRowCount: number;
|
|
25
|
+
readonly ledgerTipHash: string;
|
|
26
|
+
readonly signoff: {
|
|
27
|
+
readonly operatorDid: string;
|
|
28
|
+
readonly ledgerStepIndex: number;
|
|
29
|
+
} | null;
|
|
30
|
+
readonly classification: ExceptionClassificationRecord | null;
|
|
31
|
+
readonly anchor: AnchorRecord | null;
|
|
32
|
+
}
|
|
33
|
+
export interface WorkCertificatePeriod {
|
|
34
|
+
readonly clientDid: string;
|
|
35
|
+
readonly clientName: string;
|
|
36
|
+
readonly month: string;
|
|
37
|
+
readonly fromIso: string;
|
|
38
|
+
readonly toIso: string;
|
|
39
|
+
}
|
|
40
|
+
export interface BuildWorkCertificateInput {
|
|
41
|
+
readonly period: WorkCertificatePeriod;
|
|
42
|
+
readonly issuerDid: string;
|
|
43
|
+
readonly records: ReadonlyArray<CertifiedWorkRecord>;
|
|
44
|
+
readonly slaTargetPct?: number;
|
|
45
|
+
}
|
|
46
|
+
export interface WorkCertificateSummary {
|
|
47
|
+
readonly workVolumeDelivered: number;
|
|
48
|
+
readonly workVolumeDeclined: number;
|
|
49
|
+
readonly automationRatePct: number;
|
|
50
|
+
readonly exceptionCount: number;
|
|
51
|
+
readonly exceptionsByClass: Record<FailureClass, number>;
|
|
52
|
+
readonly humanSignoffCount: number;
|
|
53
|
+
readonly slaAdherencePct: number;
|
|
54
|
+
readonly slaTargetPct: number | null;
|
|
55
|
+
readonly anchoredCount: number;
|
|
56
|
+
}
|
|
57
|
+
export interface Aiuc1DomainCoverage extends Aiuc1Domain {
|
|
58
|
+
readonly evidence: ReadonlyArray<string>;
|
|
59
|
+
readonly status: "evidenced" | "partial";
|
|
60
|
+
}
|
|
61
|
+
export interface CertifiedOutcome {
|
|
62
|
+
readonly workItemId: string;
|
|
63
|
+
readonly workflowId: string;
|
|
64
|
+
readonly status: WorkOutcomeStatus;
|
|
65
|
+
readonly slaMet: boolean;
|
|
66
|
+
readonly completedAtIso: string;
|
|
67
|
+
readonly ledgerTipHash: string;
|
|
68
|
+
readonly signoffLedgerStepIndex: number | null;
|
|
69
|
+
readonly classificationId: string | null;
|
|
70
|
+
readonly failureClass: FailureClass | null;
|
|
71
|
+
readonly anchorMerkleRoot: string | null;
|
|
72
|
+
readonly anchorWitnessBackend: string | null;
|
|
73
|
+
}
|
|
74
|
+
export interface WorkCertificateAnchorRef {
|
|
75
|
+
readonly merkleRoot: string;
|
|
76
|
+
readonly witnessBackend: string;
|
|
77
|
+
readonly witnessedAtIso: string | null;
|
|
78
|
+
readonly verifyWith: string;
|
|
79
|
+
}
|
|
80
|
+
export interface WorkCertificateBody {
|
|
81
|
+
readonly formatVersion: string;
|
|
82
|
+
readonly standard: {
|
|
83
|
+
readonly name: "AIUC-1";
|
|
84
|
+
readonly source: string;
|
|
85
|
+
};
|
|
86
|
+
readonly period: WorkCertificatePeriod;
|
|
87
|
+
readonly issuerDid: string;
|
|
88
|
+
readonly issuedAtIso: string;
|
|
89
|
+
readonly summary: WorkCertificateSummary;
|
|
90
|
+
readonly aiuc1Coverage: ReadonlyArray<Aiuc1DomainCoverage>;
|
|
91
|
+
readonly outcomes: ReadonlyArray<CertifiedOutcome>;
|
|
92
|
+
readonly anchors: ReadonlyArray<WorkCertificateAnchorRef>;
|
|
93
|
+
readonly verification: {
|
|
94
|
+
readonly instructions: string;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export interface WorkCertificate {
|
|
98
|
+
readonly body: WorkCertificateBody;
|
|
99
|
+
readonly envelope: DsseEnvelope;
|
|
100
|
+
}
|
|
101
|
+
/** Build + DSSE-sign a Work Certificate from a period's ledger-derived records. */
|
|
102
|
+
export declare function buildWorkCertificate(input: BuildWorkCertificateInput, issuer: DsseSigner, issuedAtIso?: string): Promise<WorkCertificate>;
|
|
103
|
+
export interface WorkCertificateVerifyReport {
|
|
104
|
+
readonly ok: boolean;
|
|
105
|
+
readonly signatureOk: boolean;
|
|
106
|
+
readonly countsRecomputeOk: boolean;
|
|
107
|
+
readonly mismatches: ReadonlyArray<string>;
|
|
108
|
+
readonly body: WorkCertificateBody | null;
|
|
109
|
+
}
|
|
110
|
+
/** Verify a Work Certificate: the DSSE signature is by the DECLARED issuer, the
|
|
111
|
+
* payload type matches, and every summary count re-derives from the enumerated
|
|
112
|
+
* outcomes. audit r1 F2: a valid signature from the wrong key, a different
|
|
113
|
+
* document type, or a malformed body must all fail — not slip through or throw. */
|
|
114
|
+
export declare function verifyWorkCertificate(envelope: DsseEnvelope, resolveIssuerKey: (keyid: string) => Uint8Array | null): Promise<WorkCertificateVerifyReport>;
|
|
115
|
+
//# sourceMappingURL=certificate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"certificate.d.ts","sourceRoot":"","sources":["../../src/certificate/certificate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// @kashscript/hudhud/certificate — the Work Certificate (AIUC-1, DSSE-signed)
|
|
3
|
+
// ----------------------------------------------------------------------------
|
|
4
|
+
// A monthly, per-client, SIGNED document rendered from the signed ledger — the
|
|
5
|
+
// billing unit (it enumerates the warranted outcomes) and the retention
|
|
6
|
+
// artifact. Everything DERIVES from the ledger: each CertifiedWorkRecord carries
|
|
7
|
+
// its evidence (chain-tip hash, sign-off row, exception classification, anchor);
|
|
8
|
+
// the certificate aggregates the counts and a third party RE-DERIVES them
|
|
9
|
+
// (verifyWorkCertificate). Sections map onto the six AIUC-1 control domains
|
|
10
|
+
// (web-verified, not invented). DSSE via `@kashscript/attest`. Ported from the
|
|
11
|
+
// oreoasis-core-host engine (M5); format/payload identifiers neutralized.
|
|
12
|
+
// ============================================================================
|
|
13
|
+
import { signEnvelope, verifyEnvelope } from "@kashscript/attest";
|
|
14
|
+
import { FAILURE_CLASSES } from "../exception";
|
|
15
|
+
export const WORK_CERTIFICATE_FORMAT_VERSION = "kash-work-certificate/1";
|
|
16
|
+
export const WORK_CERTIFICATE_PAYLOAD_TYPE = "application/vnd.kash.work-certificate+json";
|
|
17
|
+
/** The AIUC-1 standard's canonical reference (web-verified). */
|
|
18
|
+
export const AIUC1_SOURCE = "https://www.aiuc-1.com/";
|
|
19
|
+
export const AIUC1_DOMAINS = [
|
|
20
|
+
{ code: "A", name: "Data & Privacy", covers: "Customer-data policy, access controls, and safeguards against data leakage / PII exposure / IP infringement / unauthorized training." },
|
|
21
|
+
{ code: "B", name: "Security", covers: "Adversarial-robustness testing, input filtering, access-privilege enforcement, deployment security, and output-exposure limits." },
|
|
22
|
+
{ code: "C", name: "Safety", covers: "Risk-taxonomy definition, harmful/out-of-scope output prevention, human-review flagging, and third-party testing." },
|
|
23
|
+
{ code: "D", name: "Reliability", covers: "Preventing hallucinated outputs and unsafe tool calls." },
|
|
24
|
+
{ code: "E", name: "Accountability", covers: "Failure-response plans, access monitoring, regulatory compliance, quality management, activity logging, and transparency." },
|
|
25
|
+
{ code: "F", name: "Society", covers: "Preventing AI cyber-misuse and catastrophic-misuse scenarios." },
|
|
26
|
+
];
|
|
27
|
+
function round1(n) {
|
|
28
|
+
return Math.round(n * 10) / 10;
|
|
29
|
+
}
|
|
30
|
+
function emptyClassMix() {
|
|
31
|
+
const mix = {};
|
|
32
|
+
for (const c of FAILURE_CLASSES)
|
|
33
|
+
mix[c] = 0;
|
|
34
|
+
return mix;
|
|
35
|
+
}
|
|
36
|
+
function summarize(records, slaTargetPct) {
|
|
37
|
+
let autonomous = 0;
|
|
38
|
+
let withSignoff = 0;
|
|
39
|
+
let declined = 0;
|
|
40
|
+
let slaMet = 0;
|
|
41
|
+
let anchored = 0;
|
|
42
|
+
const exceptionsByClass = emptyClassMix();
|
|
43
|
+
for (const r of records) {
|
|
44
|
+
if (r.status === "delivered_autonomous")
|
|
45
|
+
autonomous += 1;
|
|
46
|
+
else if (r.status === "delivered_with_signoff")
|
|
47
|
+
withSignoff += 1;
|
|
48
|
+
else
|
|
49
|
+
declined += 1;
|
|
50
|
+
if (r.slaMet)
|
|
51
|
+
slaMet += 1;
|
|
52
|
+
if (r.anchor !== null)
|
|
53
|
+
anchored += 1;
|
|
54
|
+
if (r.classification !== null)
|
|
55
|
+
exceptionsByClass[r.classification.failureClass] += 1;
|
|
56
|
+
}
|
|
57
|
+
const delivered = autonomous + withSignoff;
|
|
58
|
+
const total = records.length;
|
|
59
|
+
return {
|
|
60
|
+
workVolumeDelivered: delivered,
|
|
61
|
+
workVolumeDeclined: declined,
|
|
62
|
+
automationRatePct: delivered === 0 ? 0 : round1((autonomous / delivered) * 100),
|
|
63
|
+
exceptionCount: records.filter((r) => r.classification !== null).length,
|
|
64
|
+
exceptionsByClass,
|
|
65
|
+
humanSignoffCount: records.filter((r) => r.signoff !== null).length,
|
|
66
|
+
slaAdherencePct: total === 0 ? 0 : round1((slaMet / total) * 100),
|
|
67
|
+
slaTargetPct: slaTargetPct ?? null,
|
|
68
|
+
anchoredCount: anchored,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function mapAiuc1Coverage(records, summary) {
|
|
72
|
+
const anchoredEvidence = summary.anchoredCount > 0
|
|
73
|
+
? `${summary.anchoredCount} of ${records.length} chain tips anchored to a public transparency log (Rekor / OpenTimestamps) — existence provable to a third party.`
|
|
74
|
+
: "Ledger is tamper-evident and independently exportable; public-witness anchoring available (none attached this period).";
|
|
75
|
+
return AIUC1_DOMAINS.map((d) => {
|
|
76
|
+
switch (d.code) {
|
|
77
|
+
case "A":
|
|
78
|
+
return { ...d, status: "evidenced", evidence: ["PII/secret redaction runs before every model call (privacy interceptor); category tags — never raw values — enter the signed ledger."] };
|
|
79
|
+
case "B":
|
|
80
|
+
return { ...d, status: "evidenced", evidence: [`Every action Ed25519-signed into an append-only, hash-chained ledger (${records.reduce((n, r) => n + r.ledgerRowCount, 0)} signed rows this period).`, anchoredEvidence] };
|
|
81
|
+
case "C":
|
|
82
|
+
return { ...d, status: "evidenced", evidence: [`${summary.exceptionCount} exception(s) flagged for human review and resolved this period; each classified under a fixed risk taxonomy (judgment / gap-filling / ambiguous).`, "Restricted actions suspend for human judgment rather than proceed autonomously."] };
|
|
83
|
+
case "D":
|
|
84
|
+
return { ...d, status: "evidenced", evidence: ["Tools restricted to a per-provider allowlist (scope-gating): an out-of-allowlist or restricted tool call cannot be dispatched by construction.", `Automation rate this period: ${summary.automationRatePct}% delivered without human intervention.`] };
|
|
85
|
+
case "E":
|
|
86
|
+
return { ...d, status: "evidenced", evidence: ["Complete signed activity log (the ledger) for every work item; a self-contained audit bundle is independently verifiable offline.", `${summary.humanSignoffCount} human sign-off(s), each a SECOND-key signed ledger row (the host cannot forge human judgment).`] };
|
|
87
|
+
case "F":
|
|
88
|
+
return { ...d, status: "partial", evidence: ["Money-moving / restricted tools require a two-key human sign-off; no autonomous execution of restricted actions in this period's ledger."] };
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function toOutcome(r) {
|
|
93
|
+
return {
|
|
94
|
+
workItemId: r.workItemId,
|
|
95
|
+
workflowId: r.workflowId,
|
|
96
|
+
status: r.status,
|
|
97
|
+
slaMet: r.slaMet,
|
|
98
|
+
completedAtIso: r.completedAtIso,
|
|
99
|
+
ledgerTipHash: r.ledgerTipHash,
|
|
100
|
+
signoffLedgerStepIndex: r.signoff?.ledgerStepIndex ?? null,
|
|
101
|
+
classificationId: r.classification?.id ?? null,
|
|
102
|
+
failureClass: r.classification?.failureClass ?? null,
|
|
103
|
+
anchorMerkleRoot: r.anchor?.merkleRoot ?? null,
|
|
104
|
+
anchorWitnessBackend: r.anchor?.witness?.backend ?? null,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function collectAnchors(records) {
|
|
108
|
+
const seen = new Set();
|
|
109
|
+
const out = [];
|
|
110
|
+
for (const r of records) {
|
|
111
|
+
const a = r.anchor;
|
|
112
|
+
if (a === null || seen.has(a.merkleRoot))
|
|
113
|
+
continue;
|
|
114
|
+
seen.add(a.merkleRoot);
|
|
115
|
+
const backend = a.witness?.backend ?? "none";
|
|
116
|
+
out.push({
|
|
117
|
+
merkleRoot: a.merkleRoot,
|
|
118
|
+
witnessBackend: backend,
|
|
119
|
+
witnessedAtIso: a.witness?.witnessedAt ?? null,
|
|
120
|
+
verifyWith: backend === "rekor"
|
|
121
|
+
? "rekor-cli get --uuid <uuid in the anchor receipt>"
|
|
122
|
+
: backend === "opentimestamps"
|
|
123
|
+
? "ots upgrade <proof>.ots && ots verify <proof>.ots"
|
|
124
|
+
: "no public witness attached (locally tamper-evident only)",
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
const VERIFY_INSTRUCTIONS = "This certificate is a DSSE envelope signed by the issuer's Ed25519 key. " +
|
|
130
|
+
"To verify: (1) check the envelope signature against the issuer DID's published key; " +
|
|
131
|
+
"(2) re-derive every summary count from the `outcomes` array (they must match `summary`); " +
|
|
132
|
+
"(3) for each outcome, re-verify the underlying audit bundle (chain + signatures) with the shipped verifier, " +
|
|
133
|
+
"and confirm the anchor with public tooling per each entry in `anchors`. Counts derive from the signed ledger — " +
|
|
134
|
+
"there is no separate metering system to trust.";
|
|
135
|
+
/** Build + DSSE-sign a Work Certificate from a period's ledger-derived records. */
|
|
136
|
+
export async function buildWorkCertificate(input, issuer, issuedAtIso = new Date().toISOString()) {
|
|
137
|
+
const summary = summarize(input.records, input.slaTargetPct);
|
|
138
|
+
const body = {
|
|
139
|
+
formatVersion: WORK_CERTIFICATE_FORMAT_VERSION,
|
|
140
|
+
standard: { name: "AIUC-1", source: AIUC1_SOURCE },
|
|
141
|
+
period: input.period,
|
|
142
|
+
issuerDid: input.issuerDid,
|
|
143
|
+
issuedAtIso,
|
|
144
|
+
summary,
|
|
145
|
+
aiuc1Coverage: mapAiuc1Coverage(input.records, summary),
|
|
146
|
+
outcomes: input.records.map(toOutcome),
|
|
147
|
+
anchors: collectAnchors(input.records),
|
|
148
|
+
verification: { instructions: VERIFY_INSTRUCTIONS },
|
|
149
|
+
};
|
|
150
|
+
const envelope = await signEnvelope(body, WORK_CERTIFICATE_PAYLOAD_TYPE, issuer);
|
|
151
|
+
return { body, envelope };
|
|
152
|
+
}
|
|
153
|
+
function recomputeCountsMatch(body) {
|
|
154
|
+
const mismatches = [];
|
|
155
|
+
const o = body.outcomes;
|
|
156
|
+
const delivered = o.filter((x) => x.status !== "declined").length;
|
|
157
|
+
const declined = o.filter((x) => x.status === "declined").length;
|
|
158
|
+
const autonomous = o.filter((x) => x.status === "delivered_autonomous").length;
|
|
159
|
+
const slaMet = o.filter((x) => x.slaMet).length;
|
|
160
|
+
const exceptionCount = o.filter((x) => x.classificationId !== null).length;
|
|
161
|
+
const signoffCount = o.filter((x) => x.signoffLedgerStepIndex !== null).length;
|
|
162
|
+
const anchored = o.filter((x) => x.anchorMerkleRoot !== null).length;
|
|
163
|
+
const total = o.length;
|
|
164
|
+
const s = body.summary;
|
|
165
|
+
const check = (label, got, want) => {
|
|
166
|
+
if (got !== want)
|
|
167
|
+
mismatches.push(`${label}: outcomes=${got} vs summary=${want}`);
|
|
168
|
+
};
|
|
169
|
+
check("workVolumeDelivered", delivered, s.workVolumeDelivered);
|
|
170
|
+
check("workVolumeDeclined", declined, s.workVolumeDeclined);
|
|
171
|
+
check("exceptionCount", exceptionCount, s.exceptionCount);
|
|
172
|
+
check("humanSignoffCount", signoffCount, s.humanSignoffCount);
|
|
173
|
+
check("anchoredCount", anchored, s.anchoredCount);
|
|
174
|
+
const wantAutomation = delivered === 0 ? 0 : round1((autonomous / delivered) * 100);
|
|
175
|
+
if (wantAutomation !== s.automationRatePct)
|
|
176
|
+
mismatches.push(`automationRatePct: recomputed=${wantAutomation} vs summary=${s.automationRatePct}`);
|
|
177
|
+
const wantSla = total === 0 ? 0 : round1((slaMet / total) * 100);
|
|
178
|
+
if (wantSla !== s.slaAdherencePct)
|
|
179
|
+
mismatches.push(`slaAdherencePct: recomputed=${wantSla} vs summary=${s.slaAdherencePct}`);
|
|
180
|
+
const classMix = emptyClassMix();
|
|
181
|
+
for (const x of o)
|
|
182
|
+
if (x.failureClass !== null)
|
|
183
|
+
classMix[x.failureClass] += 1;
|
|
184
|
+
for (const c of FAILURE_CLASSES) {
|
|
185
|
+
if (classMix[c] !== s.exceptionsByClass[c])
|
|
186
|
+
mismatches.push(`exceptionsByClass.${c}: outcomes=${classMix[c]} vs summary=${s.exceptionsByClass[c]}`);
|
|
187
|
+
}
|
|
188
|
+
return mismatches;
|
|
189
|
+
}
|
|
190
|
+
function isWorkCertificateBody(x) {
|
|
191
|
+
if (x === null || typeof x !== "object")
|
|
192
|
+
return false;
|
|
193
|
+
const b = x;
|
|
194
|
+
return (typeof b.issuerDid === "string" &&
|
|
195
|
+
Array.isArray(b.outcomes) &&
|
|
196
|
+
b.summary !== null &&
|
|
197
|
+
typeof b.summary === "object");
|
|
198
|
+
}
|
|
199
|
+
/** Verify a Work Certificate: the DSSE signature is by the DECLARED issuer, the
|
|
200
|
+
* payload type matches, and every summary count re-derives from the enumerated
|
|
201
|
+
* outcomes. audit r1 F2: a valid signature from the wrong key, a different
|
|
202
|
+
* document type, or a malformed body must all fail — not slip through or throw. */
|
|
203
|
+
export async function verifyWorkCertificate(envelope, resolveIssuerKey) {
|
|
204
|
+
const dsse = await verifyEnvelope(envelope, resolveIssuerKey);
|
|
205
|
+
if (dsse.payloadType !== WORK_CERTIFICATE_PAYLOAD_TYPE) {
|
|
206
|
+
return { ok: false, signatureOk: dsse.ok, countsRecomputeOk: false, mismatches: [`payloadType is "${dsse.payloadType}", expected "${WORK_CERTIFICATE_PAYLOAD_TYPE}"`], body: null };
|
|
207
|
+
}
|
|
208
|
+
const body = isWorkCertificateBody(dsse.payloadJson) ? dsse.payloadJson : null;
|
|
209
|
+
if (!dsse.ok || body === null) {
|
|
210
|
+
return { ok: false, signatureOk: dsse.ok, countsRecomputeOk: false, mismatches: body === null ? ["malformed work-certificate body"] : [], body };
|
|
211
|
+
}
|
|
212
|
+
// The signer must be the issuer the certificate NAMES. A valid signature from
|
|
213
|
+
// any OTHER resolvable key is not a valid certificate from `issuerDid`.
|
|
214
|
+
if (!dsse.verifiedKeyids.includes(body.issuerDid)) {
|
|
215
|
+
return { ok: false, signatureOk: true, countsRecomputeOk: false, mismatches: [`signer ${JSON.stringify(dsse.verifiedKeyids)} is not the declared issuer "${body.issuerDid}"`], body };
|
|
216
|
+
}
|
|
217
|
+
const mismatches = recomputeCountsMatch(body);
|
|
218
|
+
const countsRecomputeOk = mismatches.length === 0;
|
|
219
|
+
return { ok: countsRecomputeOk, signatureOk: true, countsRecomputeOk, mismatches, body };
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=certificate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"certificate.js","sourceRoot":"","sources":["../../src/certificate/certificate.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,8EAA8E;AAC9E,+EAA+E;AAC/E,+EAA+E;AAC/E,wEAAwE;AACxE,iFAAiF;AACjF,iFAAiF;AACjF,0EAA0E;AAC1E,4EAA4E;AAC5E,+EAA+E;AAC/E,0EAA0E;AAC1E,+EAA+E;AAE/E,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAIlE,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAG/C,MAAM,CAAC,MAAM,+BAA+B,GAAG,yBAAkC,CAAC;AAClF,MAAM,CAAC,MAAM,6BAA6B,GAAG,4CAAqD,CAAC;AAEnG,gEAAgE;AAChE,MAAM,CAAC,MAAM,YAAY,GAAG,yBAAkC,CAAC;AAQ/D,MAAM,CAAC,MAAM,aAAa,GAA+B;IACvD,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,gBAAgB,EAAE,MAAM,EAAE,sIAAsI,EAAE;IACrL,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,iIAAiI,EAAE;IAC1K,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,mHAAmH,EAAE;IAC1J,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,wDAAwD,EAAE;IACpG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,gBAAgB,EAAE,MAAM,EAAE,2HAA2H,EAAE;IAC1K,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,+DAA+D,EAAE;CACxG,CAAC;AA2FF,SAAS,MAAM,CAAC,CAAS;IACvB,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC;AACjC,CAAC;AACD,SAAS,aAAa;IACpB,MAAM,GAAG,GAAG,EAAkC,CAAC;IAC/C,KAAK,MAAM,CAAC,IAAI,eAAe;QAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAC5C,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,SAAS,CAAC,OAA2C,EAAE,YAAgC;IAC9F,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,MAAM,iBAAiB,GAAG,aAAa,EAAE,CAAC;IAC1C,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,IAAI,CAAC,CAAC,MAAM,KAAK,sBAAsB;YAAE,UAAU,IAAI,CAAC,CAAC;aACpD,IAAI,CAAC,CAAC,MAAM,KAAK,wBAAwB;YAAE,WAAW,IAAI,CAAC,CAAC;;YAC5D,QAAQ,IAAI,CAAC,CAAC;QACnB,IAAI,CAAC,CAAC,MAAM;YAAE,MAAM,IAAI,CAAC,CAAC;QAC1B,IAAI,CAAC,CAAC,MAAM,KAAK,IAAI;YAAE,QAAQ,IAAI,CAAC,CAAC;QACrC,IAAI,CAAC,CAAC,cAAc,KAAK,IAAI;YAAE,iBAAiB,CAAC,CAAC,CAAC,cAAc,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IACvF,CAAC;IACD,MAAM,SAAS,GAAG,UAAU,GAAG,WAAW,CAAC;IAC3C,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC;IAC7B,OAAO;QACL,mBAAmB,EAAE,SAAS;QAC9B,kBAAkB,EAAE,QAAQ;QAC5B,iBAAiB,EAAE,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,UAAU,GAAG,SAAS,CAAC,GAAG,GAAG,CAAC;QAC/E,cAAc,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,KAAK,IAAI,CAAC,CAAC,MAAM;QACvE,iBAAiB;QACjB,iBAAiB,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,MAAM;QACnE,eAAe,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC;QACjE,YAAY,EAAE,YAAY,IAAI,IAAI;QAClC,aAAa,EAAE,QAAQ;KACxB,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,OAA2C,EAAE,OAA+B;IACpG,MAAM,gBAAgB,GACpB,OAAO,CAAC,aAAa,GAAG,CAAC;QACvB,CAAC,CAAC,GAAG,OAAO,CAAC,aAAa,OAAO,OAAO,CAAC,MAAM,mHAAmH;QAClK,CAAC,CAAC,wHAAwH,CAAC;IAC/H,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAuB,EAAE;QAClD,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;YACf,KAAK,GAAG;gBACN,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,sIAAsI,CAAC,EAAE,CAAC;YAC3L,KAAK,GAAG;gBACN,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,yEAAyE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC,4BAA4B,EAAE,gBAAgB,CAAC,EAAE,CAAC;YAC7N,KAAK,GAAG;gBACN,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,cAAc,oJAAoJ,EAAE,iFAAiF,CAAC,EAAE,CAAC;YACrT,KAAK,GAAG;gBACN,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,gJAAgJ,EAAE,gCAAgC,OAAO,CAAC,iBAAiB,yCAAyC,CAAC,EAAE,CAAC;YACzS,KAAK,GAAG;gBACN,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,mIAAmI,EAAE,GAAG,OAAO,CAAC,iBAAiB,iGAAiG,CAAC,EAAE,CAAC;YACvT,KAAK,GAAG;gBACN,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,0IAA0I,CAAC,EAAE,CAAC;QAC/L,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,SAAS,CAAC,CAAsB;IACvC,OAAO;QACL,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,cAAc,EAAE,CAAC,CAAC,cAAc;QAChC,aAAa,EAAE,CAAC,CAAC,aAAa;QAC9B,sBAAsB,EAAE,CAAC,CAAC,OAAO,EAAE,eAAe,IAAI,IAAI;QAC1D,gBAAgB,EAAE,CAAC,CAAC,cAAc,EAAE,EAAE,IAAI,IAAI;QAC9C,YAAY,EAAE,CAAC,CAAC,cAAc,EAAE,YAAY,IAAI,IAAI;QACpD,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,UAAU,IAAI,IAAI;QAC9C,oBAAoB,EAAE,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,IAAI,IAAI;KACzD,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,OAA2C;IACjE,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,GAAG,GAA+B,EAAE,CAAC;IAC3C,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;QACnB,IAAI,CAAC,KAAK,IAAI,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC;YAAE,SAAS;QACnD,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;QACvB,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,EAAE,OAAO,IAAI,MAAM,CAAC;QAC7C,GAAG,CAAC,IAAI,CAAC;YACP,UAAU,EAAE,CAAC,CAAC,UAAU;YACxB,cAAc,EAAE,OAAO;YACvB,cAAc,EAAE,CAAC,CAAC,OAAO,EAAE,WAAW,IAAI,IAAI;YAC9C,UAAU,EACR,OAAO,KAAK,OAAO;gBACjB,CAAC,CAAC,mDAAmD;gBACrD,CAAC,CAAC,OAAO,KAAK,gBAAgB;oBAC5B,CAAC,CAAC,mDAAmD;oBACrD,CAAC,CAAC,0DAA0D;SACnE,CAAC,CAAC;IACL,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,mBAAmB,GACvB,0EAA0E;IAC1E,sFAAsF;IACtF,2FAA2F;IAC3F,8GAA8G;IAC9G,iHAAiH;IACjH,gDAAgD,CAAC;AAEnD,mFAAmF;AACnF,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,KAAgC,EAChC,MAAkB,EAClB,cAAsB,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;IAE9C,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;IAC7D,MAAM,IAAI,GAAwB;QAChC,aAAa,EAAE,+BAA+B;QAC9C,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE;QAClD,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,WAAW;QACX,OAAO;QACP,aAAa,EAAE,gBAAgB,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC;QACvD,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC;QACtC,OAAO,EAAE,cAAc,CAAC,KAAK,CAAC,OAAO,CAAC;QACtC,YAAY,EAAE,EAAE,YAAY,EAAE,mBAAmB,EAAE;KACpD,CAAC;IACF,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,IAAI,EAAE,6BAA6B,EAAE,MAAM,CAAC,CAAC;IACjF,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAC5B,CAAC;AAUD,SAAS,oBAAoB,CAAC,IAAyB;IACrD,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC;IACxB,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,MAAM,CAAC;IAClE,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,MAAM,CAAC;IACjE,MAAM,UAAU,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,sBAAsB,CAAC,CAAC,MAAM,CAAC;IAC/E,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC;IAChD,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,gBAAgB,KAAK,IAAI,CAAC,CAAC,MAAM,CAAC;IAC3E,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,sBAAsB,KAAK,IAAI,CAAC,CAAC,MAAM,CAAC;IAC/E,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,gBAAgB,KAAK,IAAI,CAAC,CAAC,MAAM,CAAC;IACrE,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC;IACvB,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC;IACvB,MAAM,KAAK,GAAG,CAAC,KAAa,EAAE,GAAW,EAAE,IAAY,EAAQ,EAAE;QAC/D,IAAI,GAAG,KAAK,IAAI;YAAE,UAAU,CAAC,IAAI,CAAC,GAAG,KAAK,cAAc,GAAG,eAAe,IAAI,EAAE,CAAC,CAAC;IACpF,CAAC,CAAC;IACF,KAAK,CAAC,qBAAqB,EAAE,SAAS,EAAE,CAAC,CAAC,mBAAmB,CAAC,CAAC;IAC/D,KAAK,CAAC,oBAAoB,EAAE,QAAQ,EAAE,CAAC,CAAC,kBAAkB,CAAC,CAAC;IAC5D,KAAK,CAAC,gBAAgB,EAAE,cAAc,EAAE,CAAC,CAAC,cAAc,CAAC,CAAC;IAC1D,KAAK,CAAC,mBAAmB,EAAE,YAAY,EAAE,CAAC,CAAC,iBAAiB,CAAC,CAAC;IAC9D,KAAK,CAAC,eAAe,EAAE,QAAQ,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC;IAClD,MAAM,cAAc,GAAG,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,UAAU,GAAG,SAAS,CAAC,GAAG,GAAG,CAAC,CAAC;IACpF,IAAI,cAAc,KAAK,CAAC,CAAC,iBAAiB;QAAE,UAAU,CAAC,IAAI,CAAC,iCAAiC,cAAc,eAAe,CAAC,CAAC,iBAAiB,EAAE,CAAC,CAAC;IACjJ,MAAM,OAAO,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC;IACjE,IAAI,OAAO,KAAK,CAAC,CAAC,eAAe;QAAE,UAAU,CAAC,IAAI,CAAC,+BAA+B,OAAO,eAAe,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC;IAC7H,MAAM,QAAQ,GAAG,aAAa,EAAE,CAAC;IACjC,KAAK,MAAM,CAAC,IAAI,CAAC;QAAE,IAAI,CAAC,CAAC,YAAY,KAAK,IAAI;YAAE,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IAC9E,KAAK,MAAM,CAAC,IAAI,eAAe,EAAE,CAAC;QAChC,IAAI,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC;YAAE,UAAU,CAAC,IAAI,CAAC,qBAAqB,CAAC,cAAc,QAAQ,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACtJ,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,SAAS,qBAAqB,CAAC,CAAU;IACvC,IAAI,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACtD,MAAM,CAAC,GAAG,CAA4B,CAAC;IACvC,OAAO,CACL,OAAO,CAAC,CAAC,SAAS,KAAK,QAAQ;QAC/B,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC;QACzB,CAAC,CAAC,OAAO,KAAK,IAAI;QAClB,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,CAC9B,CAAC;AACJ,CAAC;AAED;;;oFAGoF;AACpF,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,QAAsB,EACtB,gBAAsD;IAEtD,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC;IAE9D,IAAI,IAAI,CAAC,WAAW,KAAK,6BAA6B,EAAE,CAAC;QACvD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE,EAAE,iBAAiB,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,mBAAmB,IAAI,CAAC,WAAW,gBAAgB,6BAA6B,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACtL,CAAC;IACD,MAAM,IAAI,GAAG,qBAAqB,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC;IAC/E,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAC9B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE,EAAE,iBAAiB,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,iCAAiC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC;IACnJ,CAAC;IACD,8EAA8E;IAC9E,wEAAwE;IACxE,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;QAClD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,iBAAiB,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,UAAU,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,cAAc,CAAC,gCAAgC,IAAI,CAAC,SAAS,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC;IACxL,CAAC;IAED,MAAM,UAAU,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;IAC9C,MAAM,iBAAiB,GAAG,UAAU,CAAC,MAAM,KAAK,CAAC,CAAC;IAClD,OAAO,EAAE,EAAE,EAAE,iBAAiB,EAAE,WAAW,EAAE,IAAI,EAAE,iBAAiB,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;AAC3F,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { WorkCertificateBody } from "./certificate";
|
|
2
|
+
export interface WorkCertificateBrand {
|
|
3
|
+
readonly accentColor?: string;
|
|
4
|
+
readonly logoText?: string;
|
|
5
|
+
/** Footer attribution line (default neutral). */
|
|
6
|
+
readonly footerText?: string;
|
|
7
|
+
}
|
|
8
|
+
/** Render a self-contained, print-to-PDF-ready HTML certificate. */
|
|
9
|
+
export declare function renderWorkCertificateHtml(body: WorkCertificateBody, brand?: WorkCertificateBrand): string;
|
|
10
|
+
//# sourceMappingURL=html.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../src/certificate/html.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAyC,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEhG,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,iDAAiD;IACjD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AA6CD,oEAAoE;AACpE,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,mBAAmB,EAAE,KAAK,GAAE,oBAAyB,GAAG,MAAM,CA6F7G"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// @kashscript/hudhud/certificate — the human-readable Work Certificate (HTML)
|
|
3
|
+
// ----------------------------------------------------------------------------
|
|
4
|
+
// A self-contained, client-branded HTML document a CFO can read and a browser
|
|
5
|
+
// can "Print → Save as PDF" (no headless-browser dependency). Renders a
|
|
6
|
+
// WorkCertificateBody; all styling inline so the file is portable. Ported from
|
|
7
|
+
// the oreoasis-core-host engine (M5); the footer is neutralized/brandable.
|
|
8
|
+
// ============================================================================
|
|
9
|
+
function esc(s) {
|
|
10
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
11
|
+
}
|
|
12
|
+
/** audit r1 F10: `accentColor` is interpolated into a CSS declaration (not an
|
|
13
|
+
* HTML text node), where `esc()` is insufficient — a `;`/`{`/`}` would break out
|
|
14
|
+
* of the rule and rewrite the document's styles (content-hiding / banner spoof).
|
|
15
|
+
* Accept ONLY a CSS colour literal, else fall back to the default. */
|
|
16
|
+
function safeAccent(s, fallback) {
|
|
17
|
+
if (s === undefined)
|
|
18
|
+
return fallback;
|
|
19
|
+
return /^(#[0-9a-fA-F]{3,8}|[a-z]{3,20}|rgb\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*\))$/.test(s) ? s : fallback;
|
|
20
|
+
}
|
|
21
|
+
/** audit r1 F10: the body can arrive from a signature-valid-but-untrusted
|
|
22
|
+
* envelope, so only hyperlink an `http(s)` source (no `javascript:` URIs). */
|
|
23
|
+
function safeUrl(s) {
|
|
24
|
+
return /^https?:\/\//i.test(s) ? s : "#";
|
|
25
|
+
}
|
|
26
|
+
function short(hash, n = 16) {
|
|
27
|
+
return hash.length > n ? `${hash.slice(0, n)}…` : hash;
|
|
28
|
+
}
|
|
29
|
+
const STATUS_LABEL = {
|
|
30
|
+
delivered_autonomous: "Delivered (autonomous)",
|
|
31
|
+
delivered_with_signoff: "Delivered (human sign-off)",
|
|
32
|
+
declined: "Declined at sign-off",
|
|
33
|
+
};
|
|
34
|
+
function metricCard(label, value, sub) {
|
|
35
|
+
return `<div class="card"><div class="card-v">${esc(value)}</div><div class="card-l">${esc(label)}</div><div class="card-s">${esc(sub)}</div></div>`;
|
|
36
|
+
}
|
|
37
|
+
function aiuc1Row(d) {
|
|
38
|
+
const badge = d.status === "evidenced" ? `<span class="pill pill-ok">Evidenced</span>` : `<span class="pill pill-part">Partial</span>`;
|
|
39
|
+
const items = d.evidence.map((e) => `<li>${esc(e)}</li>`).join("");
|
|
40
|
+
return `<tr><td class="dom"><span class="dom-code">${esc(d.code)}</span> ${esc(d.name)} ${badge}<div class="dom-covers">${esc(d.covers)}</div></td><td><ul class="ev">${items}</ul></td></tr>`;
|
|
41
|
+
}
|
|
42
|
+
function outcomeRow(o) {
|
|
43
|
+
const cls = o.failureClass ? esc(o.failureClass) : "—";
|
|
44
|
+
const anchor = o.anchorMerkleRoot ? `${esc(short(o.anchorMerkleRoot, 12))} <span class="mut">(${esc(o.anchorWitnessBackend ?? "none")})</span>` : "—";
|
|
45
|
+
const sla = o.slaMet ? `<span class="pill pill-ok">met</span>` : `<span class="pill pill-bad">missed</span>`;
|
|
46
|
+
return `<tr><td class="mono">${esc(o.workItemId)}</td><td>${esc(STATUS_LABEL[o.status])}</td><td>${sla}</td><td class="mono">${esc(short(o.ledgerTipHash, 12))}</td><td>${o.signoffLedgerStepIndex === null ? "—" : `#${o.signoffLedgerStepIndex}`}</td><td>${cls}</td><td class="mono">${anchor}</td></tr>`;
|
|
47
|
+
}
|
|
48
|
+
/** Render a self-contained, print-to-PDF-ready HTML certificate. */
|
|
49
|
+
export function renderWorkCertificateHtml(body, brand = {}) {
|
|
50
|
+
const accent = safeAccent(brand.accentColor, "#0f2a4a");
|
|
51
|
+
const logo = brand.logoText ?? body.period.clientName;
|
|
52
|
+
const footer = brand.footerText ?? "Accountable AI workforce";
|
|
53
|
+
const s = body.summary;
|
|
54
|
+
const classMix = Object.entries(s.exceptionsByClass).map(([k, v]) => `${esc(k)}: <b>${v}</b>`).join(" · ");
|
|
55
|
+
const anchors = body.anchors.length === 0
|
|
56
|
+
? `<p class="mut">No public-witness anchor attached to this period (ledger is tamper-evident and independently exportable).</p>`
|
|
57
|
+
: `<table class="tbl"><thead><tr><th>Merkle root</th><th>Witness</th><th>Witnessed</th><th>Verify with</th></tr></thead><tbody>${body.anchors
|
|
58
|
+
.map((a) => `<tr><td class="mono">${esc(short(a.merkleRoot, 20))}</td><td>${esc(a.witnessBackend)}</td><td>${esc(a.witnessedAtIso ?? "pending")}</td><td class="mono">${esc(a.verifyWith)}</td></tr>`)
|
|
59
|
+
.join("")}</tbody></table>`;
|
|
60
|
+
return `<!doctype html>
|
|
61
|
+
<html lang="en"><head><meta charset="utf-8">
|
|
62
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
63
|
+
<title>Work Certificate — ${esc(body.period.clientName)} — ${esc(body.period.month)}</title>
|
|
64
|
+
<style>
|
|
65
|
+
:root { --accent: ${esc(accent)}; }
|
|
66
|
+
* { box-sizing: border-box; }
|
|
67
|
+
body { font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; color: #1a1f26; margin: 0; background: #f4f6f8; }
|
|
68
|
+
.page { max-width: 900px; margin: 24px auto; background: #fff; box-shadow: 0 1px 6px rgba(0,0,0,.08); }
|
|
69
|
+
.band { background: var(--accent); color: #fff; padding: 28px 40px; }
|
|
70
|
+
.band .logo { font-size: 13px; letter-spacing: .18em; text-transform: uppercase; opacity: .85; }
|
|
71
|
+
.band h1 { margin: 6px 0 2px; font-size: 26px; font-weight: 650; }
|
|
72
|
+
.band .sub { opacity: .9; font-size: 14px; }
|
|
73
|
+
.band .verifiable { margin-top: 12px; display: inline-block; font-size: 12px; background: rgba(255,255,255,.16); padding: 4px 10px; border-radius: 3px; }
|
|
74
|
+
.body { padding: 28px 40px 40px; }
|
|
75
|
+
h2 { font-size: 15px; text-transform: uppercase; letter-spacing: .06em; color: var(--accent); border-bottom: 2px solid #eceff2; padding-bottom: 6px; margin: 30px 0 14px; }
|
|
76
|
+
.cards { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
|
|
77
|
+
.card { border: 1px solid #e6eaee; border-radius: 5px; padding: 14px; }
|
|
78
|
+
.card-v { font-size: 24px; font-weight: 680; color: var(--accent); }
|
|
79
|
+
.card-l { font-size: 12px; font-weight: 600; margin-top: 2px; }
|
|
80
|
+
.card-s { font-size: 11px; color: #6b7683; margin-top: 2px; }
|
|
81
|
+
table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
|
|
82
|
+
.aiuc td { border-top: 1px solid #eceff2; padding: 12px 8px; vertical-align: top; }
|
|
83
|
+
.aiuc .dom { width: 40%; }
|
|
84
|
+
.dom-code { display: inline-block; background: var(--accent); color: #fff; width: 20px; height: 20px; text-align: center; line-height: 20px; border-radius: 3px; font-size: 11px; font-weight: 700; margin-right: 4px; }
|
|
85
|
+
.dom-covers { color: #6b7683; font-size: 11.5px; margin-top: 4px; }
|
|
86
|
+
ul.ev { margin: 0; padding-left: 16px; } ul.ev li { margin: 3px 0; }
|
|
87
|
+
.tbl th { text-align: left; background: #f7f9fb; border-bottom: 1px solid #e6eaee; padding: 8px; font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: #6b7683; }
|
|
88
|
+
.tbl td { border-bottom: 1px solid #f0f2f5; padding: 8px; }
|
|
89
|
+
.mono { font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace; font-size: 11.5px; }
|
|
90
|
+
.mut { color: #94a0ac; }
|
|
91
|
+
.pill { font-size: 10.5px; padding: 1px 7px; border-radius: 10px; font-weight: 600; }
|
|
92
|
+
.pill-ok { background: #e3f5ea; color: #1a7a44; }
|
|
93
|
+
.pill-part { background: #fbf1dc; color: #8a6414; }
|
|
94
|
+
.pill-bad { background: #fbe4e4; color: #a52222; }
|
|
95
|
+
.verify { background: #f7f9fb; border: 1px solid #e6eaee; border-radius: 5px; padding: 14px 16px; font-size: 12.5px; }
|
|
96
|
+
.foot { color: #94a0ac; font-size: 11px; margin-top: 24px; border-top: 1px solid #eceff2; padding-top: 12px; }
|
|
97
|
+
a { color: var(--accent); }
|
|
98
|
+
@media print { body { background: #fff; } .page { box-shadow: none; margin: 0; max-width: none; } h2 { break-after: avoid; } tr { break-inside: avoid; } }
|
|
99
|
+
</style></head>
|
|
100
|
+
<body><div class="page">
|
|
101
|
+
<div class="band">
|
|
102
|
+
<div class="logo">${esc(logo)}</div>
|
|
103
|
+
<h1>Work Certificate</h1>
|
|
104
|
+
<div class="sub">${esc(body.period.clientName)} · ${esc(body.period.month)} · ${esc(body.period.fromIso.slice(0, 10))} → ${esc(body.period.toIso.slice(0, 10))}</div>
|
|
105
|
+
<div class="verifiable">🔒 Cryptographically signed & independently verifiable — structured to AIUC-1</div>
|
|
106
|
+
</div>
|
|
107
|
+
<div class="body">
|
|
108
|
+
<h2>Delivered work this period</h2>
|
|
109
|
+
<div class="cards">
|
|
110
|
+
${metricCard("Outcomes delivered", String(s.workVolumeDelivered), `${s.workVolumeDeclined} declined at sign-off`)}
|
|
111
|
+
${metricCard("Automation rate", `${s.automationRatePct}%`, "delivered without human touch")}
|
|
112
|
+
${metricCard("Exceptions", String(s.exceptionCount), `${s.humanSignoffCount} human sign-off(s)`)}
|
|
113
|
+
${metricCard("SLA adherence", `${s.slaAdherencePct}%`, s.slaTargetPct === null ? "no target set" : `target ${s.slaTargetPct}%`)}
|
|
114
|
+
</div>
|
|
115
|
+
<p style="font-size:12.5px;color:#6b7683;margin-top:12px">Exception classification mix — ${classMix}. Every count on this certificate derives from the signed ledger; there is no separate metering system.</p>
|
|
116
|
+
|
|
117
|
+
<h2>AIUC-1 control coverage</h2>
|
|
118
|
+
<table class="aiuc"><tbody>
|
|
119
|
+
${body.aiuc1Coverage.map(aiuc1Row).join("\n")}
|
|
120
|
+
</tbody></table>
|
|
121
|
+
<p class="mut" style="font-size:11px;margin-top:8px">Categories per the AIUC-1 standard (<a href="${esc(safeUrl(body.standard.source))}">${esc(body.standard.source)}</a>). Evidence statements are factual records from this period's signed ledger.</p>
|
|
122
|
+
|
|
123
|
+
<h2>Warranted outcomes (${body.outcomes.length})</h2>
|
|
124
|
+
<table class="tbl"><thead><tr><th>Work item</th><th>Outcome</th><th>SLA</th><th>Chain tip</th><th>Sign-off</th><th>Class</th><th>Anchor</th></tr></thead>
|
|
125
|
+
<tbody>${body.outcomes.map(outcomeRow).join("\n")}</tbody></table>
|
|
126
|
+
|
|
127
|
+
<h2>Public-witness anchors</h2>
|
|
128
|
+
${anchors}
|
|
129
|
+
|
|
130
|
+
<h2>How to verify this certificate</h2>
|
|
131
|
+
<div class="verify">
|
|
132
|
+
<p style="margin:0 0 8px">${esc(body.verification.instructions)}</p>
|
|
133
|
+
<div class="mono">issuer: ${esc(body.issuerDid)}</div>
|
|
134
|
+
<div class="mono">format: ${esc(body.formatVersion)} · issued: ${esc(body.issuedAtIso)}</div>
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<div class="foot">${esc(footer)}. This certificate enumerates the warranted outcomes it is the billing unit for. AIUC-1 categories: ${esc(body.standard.source)}.</div>
|
|
138
|
+
</div>
|
|
139
|
+
</div></body></html>`;
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=html.js.map
|