@cogenta/plugins 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/dist/guest/sandbox-entry.mjs +155 -0
- package/dist/host/capabilities.d.ts +46 -0
- package/dist/host/capabilities.d.ts.map +1 -0
- package/dist/host/capabilities.js +105 -0
- package/dist/host/capabilities.js.map +1 -0
- package/dist/host/protocol.d.ts +58 -0
- package/dist/host/protocol.d.ts.map +1 -0
- package/dist/host/protocol.js +10 -0
- package/dist/host/protocol.js.map +1 -0
- package/dist/host/worker-runner.d.ts +92 -0
- package/dist/host/worker-runner.d.ts.map +1 -0
- package/dist/host/worker-runner.js +196 -0
- package/dist/host/worker-runner.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/loader.d.ts +81 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +190 -0
- package/dist/loader.js.map +1 -0
- package/dist/manifest.d.ts +93 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +197 -0
- package/dist/manifest.js.map +1 -0
- package/dist/permissions/describe.d.ts +22 -0
- package/dist/permissions/describe.d.ts.map +1 -0
- package/dist/permissions/describe.js +158 -0
- package/dist/permissions/describe.js.map +1 -0
- package/dist/permissions/disabled.d.ts +25 -0
- package/dist/permissions/disabled.d.ts.map +1 -0
- package/dist/permissions/disabled.js +29 -0
- package/dist/permissions/disabled.js.map +1 -0
- package/dist/permissions/grants.d.ts +22 -0
- package/dist/permissions/grants.d.ts.map +1 -0
- package/dist/permissions/grants.js +31 -0
- package/dist/permissions/grants.js.map +1 -0
- package/dist/permissions/resolve.d.ts +47 -0
- package/dist/permissions/resolve.d.ts.map +1 -0
- package/dist/permissions/resolve.js +51 -0
- package/dist/permissions/resolve.js.map +1 -0
- package/dist/permissions/review.d.ts +50 -0
- package/dist/permissions/review.d.ts.map +1 -0
- package/dist/permissions/review.js +46 -0
- package/dist/permissions/review.js.map +1 -0
- package/dist/permissions/tables.d.ts +18 -0
- package/dist/permissions/tables.d.ts.map +1 -0
- package/dist/permissions/tables.js +48 -0
- package/dist/permissions/tables.js.map +1 -0
- package/dist/registries/plugins.d.ts +74 -0
- package/dist/registries/plugins.d.ts.map +1 -0
- package/dist/registries/plugins.js +127 -0
- package/dist/registries/plugins.js.map +1 -0
- package/dist/registries/skills.d.ts +54 -0
- package/dist/registries/skills.d.ts.map +1 -0
- package/dist/registries/skills.js +113 -0
- package/dist/registries/skills.js.map +1 -0
- package/dist/registries/skins.d.ts +33 -0
- package/dist/registries/skins.d.ts.map +1 -0
- package/dist/registries/skins.js +65 -0
- package/dist/registries/skins.js.map +1 -0
- package/dist/registries/tables.d.ts +44 -0
- package/dist/registries/tables.d.ts.map +1 -0
- package/dist/registries/tables.js +118 -0
- package/dist/registries/tables.js.map +1 -0
- package/dist/registries/themes.d.ts +59 -0
- package/dist/registries/themes.d.ts.map +1 -0
- package/dist/registries/themes.js +82 -0
- package/dist/registries/themes.js.map +1 -0
- package/dist/semver.d.ts +24 -0
- package/dist/semver.d.ts.map +1 -0
- package/dist/semver.js +93 -0
- package/dist/semver.js.map +1 -0
- package/dist/signing/keys.d.ts +18 -0
- package/dist/signing/keys.d.ts.map +1 -0
- package/dist/signing/keys.js +15 -0
- package/dist/signing/keys.js.map +1 -0
- package/dist/signing/sign.d.ts +35 -0
- package/dist/signing/sign.d.ts.map +1 -0
- package/dist/signing/sign.js +63 -0
- package/dist/signing/sign.js.map +1 -0
- package/dist/signing/verify.d.ts +40 -0
- package/dist/signing/verify.d.ts.map +1 -0
- package/dist/signing/verify.js +73 -0
- package/dist/signing/verify.js.map +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { parseSkillFile } from '@cogenta/agents';
|
|
2
|
+
import { CogentaError, identifier, newId, sql } from '@cogenta/core';
|
|
3
|
+
import { REGISTRY_TABLES } from './tables.js';
|
|
4
|
+
function toEntry(row) {
|
|
5
|
+
const status = row.status === 'accepted' ? 'accepted' : row.status === 'pending' ? 'pending' : 'rejected';
|
|
6
|
+
return {
|
|
7
|
+
id: row.id,
|
|
8
|
+
submitterId: row.submitter_id,
|
|
9
|
+
displayName: row.display_name,
|
|
10
|
+
description: row.description,
|
|
11
|
+
status,
|
|
12
|
+
skillName: row.skill_name,
|
|
13
|
+
skillVersion: row.skill_version,
|
|
14
|
+
rejectionCode: row.rejection_code,
|
|
15
|
+
rejectionReason: row.rejection_reason,
|
|
16
|
+
reviewedBy: row.reviewed_by,
|
|
17
|
+
reviewedAt: row.reviewed_at,
|
|
18
|
+
submittedAt: row.submitted_at,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function createSkillRegistry(db, now = Date.now) {
|
|
22
|
+
const skills = identifier(REGISTRY_TABLES.skills, db.dialect);
|
|
23
|
+
return {
|
|
24
|
+
async submit(input) {
|
|
25
|
+
const id = newId(now);
|
|
26
|
+
const submittedAt = new Date(now()).toISOString();
|
|
27
|
+
let status = 'pending';
|
|
28
|
+
let skillName = null;
|
|
29
|
+
let skillVersion = null;
|
|
30
|
+
let rejectionCode = null;
|
|
31
|
+
let rejectionReason = null;
|
|
32
|
+
try {
|
|
33
|
+
const { metadata } = parseSkillFile(input.displayName, input.rawContent);
|
|
34
|
+
skillName = metadata.name;
|
|
35
|
+
skillVersion = metadata.version;
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
status = 'rejected';
|
|
39
|
+
rejectionCode = error instanceof CogentaError ? error.code : 'SKILL_DEFINITION_INVALID';
|
|
40
|
+
rejectionReason = error instanceof Error ? error.message : String(error);
|
|
41
|
+
}
|
|
42
|
+
const description = input.description ?? null;
|
|
43
|
+
await db.query(sql `
|
|
44
|
+
insert into ${skills}
|
|
45
|
+
(id, submitter_id, display_name, description, raw_content, skill_name, skill_version,
|
|
46
|
+
status, rejection_code, rejection_reason, reviewed_by, reviewed_at, submitted_at)
|
|
47
|
+
values
|
|
48
|
+
(${id}, ${input.submitterId}, ${input.displayName}, ${description}, ${input.rawContent},
|
|
49
|
+
${skillName}, ${skillVersion}, ${status}, ${rejectionCode}, ${rejectionReason},
|
|
50
|
+
${null}, ${null}, ${submittedAt})`);
|
|
51
|
+
return {
|
|
52
|
+
id,
|
|
53
|
+
submitterId: input.submitterId,
|
|
54
|
+
displayName: input.displayName,
|
|
55
|
+
description,
|
|
56
|
+
status,
|
|
57
|
+
skillName,
|
|
58
|
+
skillVersion,
|
|
59
|
+
rejectionCode,
|
|
60
|
+
rejectionReason,
|
|
61
|
+
reviewedBy: null,
|
|
62
|
+
reviewedAt: null,
|
|
63
|
+
submittedAt,
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
async review(id, decision, reviewerUserId, notes) {
|
|
67
|
+
const result = await db.query(sql `
|
|
68
|
+
select id, submitter_id, display_name, description, skill_name, skill_version, status,
|
|
69
|
+
rejection_code, rejection_reason, reviewed_by, reviewed_at, submitted_at
|
|
70
|
+
from ${skills} where id = ${id}`);
|
|
71
|
+
const row = result.rows[0];
|
|
72
|
+
if (row === undefined)
|
|
73
|
+
return { ok: false, reason: 'not_found' };
|
|
74
|
+
const current = toEntry(row);
|
|
75
|
+
if (current.status !== 'pending')
|
|
76
|
+
return { ok: false, reason: 'already_decided', entry: current };
|
|
77
|
+
const reviewedAt = new Date(now()).toISOString();
|
|
78
|
+
const nextStatus = decision === 'accept' ? 'accepted' : 'rejected';
|
|
79
|
+
const rejectionReason = decision === 'reject' ? (notes ?? null) : null;
|
|
80
|
+
await db.query(sql `
|
|
81
|
+
update ${skills}
|
|
82
|
+
set status = ${nextStatus}, rejection_reason = ${rejectionReason},
|
|
83
|
+
reviewed_by = ${reviewerUserId}, reviewed_at = ${reviewedAt}
|
|
84
|
+
where id = ${id}`);
|
|
85
|
+
return {
|
|
86
|
+
ok: true,
|
|
87
|
+
entry: {
|
|
88
|
+
...current,
|
|
89
|
+
status: nextStatus,
|
|
90
|
+
rejectionReason,
|
|
91
|
+
reviewedBy: reviewerUserId,
|
|
92
|
+
reviewedAt,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
async listAccepted() {
|
|
97
|
+
const result = await db.query(sql `
|
|
98
|
+
select id, submitter_id, display_name, description, skill_name, skill_version, status,
|
|
99
|
+
rejection_code, rejection_reason, reviewed_by, reviewed_at, submitted_at
|
|
100
|
+
from ${skills} where status = 'accepted' order by submitted_at asc`);
|
|
101
|
+
return result.rows.map(toEntry);
|
|
102
|
+
},
|
|
103
|
+
async get(id) {
|
|
104
|
+
const result = await db.query(sql `
|
|
105
|
+
select id, submitter_id, display_name, description, skill_name, skill_version, status,
|
|
106
|
+
rejection_code, rejection_reason, reviewed_by, reviewed_at, submitted_at
|
|
107
|
+
from ${skills} where id = ${id}`);
|
|
108
|
+
const row = result.rows[0];
|
|
109
|
+
return row === undefined ? null : toEntry(row);
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=skills.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.js","sourceRoot":"","sources":["../../src/registries/skills.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,EAAE,YAAY,EAAuB,UAAU,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAA;AACzF,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAwE7C,SAAS,OAAO,CAAC,GAAa;IAC5B,MAAM,MAAM,GACV,GAAG,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAA;IAC5F,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,WAAW,EAAE,GAAG,CAAC,YAAY;QAC7B,WAAW,EAAE,GAAG,CAAC,YAAY;QAC7B,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,MAAM;QACN,SAAS,EAAE,GAAG,CAAC,UAAU;QACzB,YAAY,EAAE,GAAG,CAAC,aAAa;QAC/B,aAAa,EAAE,GAAG,CAAC,cAAc;QACjC,eAAe,EAAE,GAAG,CAAC,gBAAgB;QACrC,UAAU,EAAE,GAAG,CAAC,WAAW;QAC3B,UAAU,EAAE,GAAG,CAAC,WAAW;QAC3B,WAAW,EAAE,GAAG,CAAC,YAAY;KAC9B,CAAA;AACH,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,EAAkB,EAClB,GAAG,GAAiB,IAAI,CAAC,GAAG;IAE5B,MAAM,MAAM,GAAG,UAAU,CAAC,eAAe,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,CAAA;IAE7D,OAAO;QACL,KAAK,CAAC,MAAM,CAAC,KAAK;YAChB,MAAM,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,CAAA;YACrB,MAAM,WAAW,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,WAAW,EAAE,CAAA;YAEjD,IAAI,MAAM,GAA0B,SAAS,CAAA;YAC7C,IAAI,SAAS,GAAkB,IAAI,CAAA;YACnC,IAAI,YAAY,GAAkB,IAAI,CAAA;YACtC,IAAI,aAAa,GAAkB,IAAI,CAAA;YACvC,IAAI,eAAe,GAAkB,IAAI,CAAA;YACzC,IAAI,CAAC;gBACH,MAAM,EAAE,QAAQ,EAAE,GAAG,cAAc,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,UAAU,CAAC,CAAA;gBACxE,SAAS,GAAG,QAAQ,CAAC,IAAI,CAAA;gBACzB,YAAY,GAAG,QAAQ,CAAC,OAAO,CAAA;YACjC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,GAAG,UAAU,CAAA;gBACnB,aAAa,GAAG,KAAK,YAAY,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,0BAA0B,CAAA;gBACvF,eAAe,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YAC1E,CAAC;YAED,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,IAAI,IAAI,CAAA;YAC7C,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,CAAA;sBACF,MAAM;;;;aAIf,EAAE,KAAK,KAAK,CAAC,WAAW,KAAK,KAAK,CAAC,WAAW,KAAK,WAAW,KAAK,KAAK,CAAC,UAAU;aACnF,SAAS,KAAK,YAAY,KAAK,MAAM,KAAK,aAAa,KAAK,eAAe;aAC3E,IAAI,KAAK,IAAI,KAAK,WAAW,GAAG,CAAC,CAAA;YAExC,OAAO;gBACL,EAAE;gBACF,WAAW,EAAE,KAAK,CAAC,WAAW;gBAC9B,WAAW,EAAE,KAAK,CAAC,WAAW;gBAC9B,WAAW;gBACX,MAAM;gBACN,SAAS;gBACT,YAAY;gBACZ,aAAa;gBACb,eAAe;gBACf,UAAU,EAAE,IAAI;gBAChB,UAAU,EAAE,IAAI;gBAChB,WAAW;aACZ,CAAA;QACH,CAAC;QAED,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,QAAQ,EAAE,cAAc,EAAE,KAAK;YAC9C,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,KAAK,CAAW,GAAG,CAAA;;;eAGlC,MAAM,eAAe,EAAE,EAAE,CAAC,CAAA;YACnC,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YAC1B,IAAI,GAAG,KAAK,SAAS;gBAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,CAAA;YAEhE,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;YAC5B,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS;gBAC9B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,KAAK,EAAE,OAAO,EAAE,CAAA;YAEjE,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,WAAW,EAAE,CAAA;YAChD,MAAM,UAAU,GAA0B,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAA;YACzF,MAAM,eAAe,GAAG,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;YAEtE,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,CAAA;iBACP,MAAM;uBACA,UAAU,wBAAwB,eAAe;4BAC5C,cAAc,mBAAmB,UAAU;qBAClD,EAAE,EAAE,CAAC,CAAA;YAEpB,OAAO;gBACL,EAAE,EAAE,IAAI;gBACR,KAAK,EAAE;oBACL,GAAG,OAAO;oBACV,MAAM,EAAE,UAAU;oBAClB,eAAe;oBACf,UAAU,EAAE,cAAc;oBAC1B,UAAU;iBACX;aACF,CAAA;QACH,CAAC;QAED,KAAK,CAAC,YAAY;YAChB,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,KAAK,CAAW,GAAG,CAAA;;;eAGlC,MAAM,sDAAsD,CAAC,CAAA;YACtE,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;QACjC,CAAC;QAED,KAAK,CAAC,GAAG,CAAC,EAAE;YACV,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,KAAK,CAAW,GAAG,CAAA;;;eAGlC,MAAM,eAAe,EAAE,EAAE,CAAC,CAAA;YACnC,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YAC1B,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAChD,CAAC;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type DatabaseHandle } from '@cogenta/core';
|
|
2
|
+
export type SkinSubmissionStatus = 'accepted' | 'rejected';
|
|
3
|
+
export interface SkinSubmissionInput {
|
|
4
|
+
readonly submitterId: string;
|
|
5
|
+
readonly displayName: string;
|
|
6
|
+
readonly description?: string;
|
|
7
|
+
/** A skin's raw candidate token JSON, unvalidated until `submit()` runs it through `validateSkin`. */
|
|
8
|
+
readonly tokens: unknown;
|
|
9
|
+
}
|
|
10
|
+
export interface SkinGalleryEntry {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly submitterId: string;
|
|
13
|
+
readonly displayName: string;
|
|
14
|
+
readonly description: string | null;
|
|
15
|
+
readonly status: SkinSubmissionStatus;
|
|
16
|
+
/** `validateSkin`'s real failure code (e.g. `SKIN_CONTRAST_INSUFFICIENT`) — `null` when accepted. */
|
|
17
|
+
readonly rejectionCode: string | null;
|
|
18
|
+
readonly rejectionReason: string | null;
|
|
19
|
+
readonly submittedAt: string;
|
|
20
|
+
}
|
|
21
|
+
export interface SkinGallery {
|
|
22
|
+
/**
|
|
23
|
+
* Runs the candidate through the real `validateSkin` (`@cogenta/render`)
|
|
24
|
+
* gate and stores the outcome either way — "sans revue humaine" (the lot's
|
|
25
|
+
* own words): there is no pending/reviewed state a human could act on,
|
|
26
|
+
* only `accepted`/`rejected`, decided once, at submission time.
|
|
27
|
+
*/
|
|
28
|
+
submit(input: SkinSubmissionInput): Promise<SkinGalleryEntry>;
|
|
29
|
+
listAccepted(): Promise<readonly SkinGalleryEntry[]>;
|
|
30
|
+
get(id: string): Promise<SkinGalleryEntry | null>;
|
|
31
|
+
}
|
|
32
|
+
export declare function createSkinGallery(db: DatabaseHandle, now?: () => number): SkinGallery;
|
|
33
|
+
//# sourceMappingURL=skins.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skins.d.ts","sourceRoot":"","sources":["../../src/registries/skins.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,cAAc,EAA0B,MAAM,eAAe,CAAA;AAIzF,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG,UAAU,CAAA;AAE1D,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,sGAAsG;IACtG,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CACzB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IACnC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAA;IACrC,qGAAqG;IACrG,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IACrC,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAC7B;AAED,MAAM,WAAW,WAAW;IAC1B;;;;;OAKG;IACH,MAAM,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAA;IAC7D,YAAY,IAAI,OAAO,CAAC,SAAS,gBAAgB,EAAE,CAAC,CAAA;IACpD,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAA;CAClD;AA0BD,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,cAAc,EAAE,GAAG,GAAE,MAAM,MAAiB,GAAG,WAAW,CAqD/F"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { CogentaError, identifier, newId, sql } from '@cogenta/core';
|
|
2
|
+
import { validateSkin } from '@cogenta/render';
|
|
3
|
+
import { REGISTRY_TABLES } from './tables.js';
|
|
4
|
+
function toEntry(row) {
|
|
5
|
+
return {
|
|
6
|
+
id: row.id,
|
|
7
|
+
submitterId: row.submitter_id,
|
|
8
|
+
displayName: row.display_name,
|
|
9
|
+
description: row.description,
|
|
10
|
+
status: row.status === 'accepted' ? 'accepted' : 'rejected',
|
|
11
|
+
rejectionCode: row.rejection_code,
|
|
12
|
+
rejectionReason: row.rejection_reason,
|
|
13
|
+
submittedAt: row.submitted_at,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function createSkinGallery(db, now = Date.now) {
|
|
17
|
+
const skins = identifier(REGISTRY_TABLES.skins, db.dialect);
|
|
18
|
+
return {
|
|
19
|
+
async submit(input) {
|
|
20
|
+
const id = newId(now);
|
|
21
|
+
const submittedAt = new Date(now()).toISOString();
|
|
22
|
+
let status = 'accepted';
|
|
23
|
+
let rejectionCode = null;
|
|
24
|
+
let rejectionReason = null;
|
|
25
|
+
try {
|
|
26
|
+
validateSkin(input.tokens);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
status = 'rejected';
|
|
30
|
+
rejectionCode = error instanceof CogentaError ? error.code : 'SKIN_VALIDATION_FAILED';
|
|
31
|
+
rejectionReason = error instanceof Error ? error.message : String(error);
|
|
32
|
+
}
|
|
33
|
+
const description = input.description ?? null;
|
|
34
|
+
await db.query(sql `
|
|
35
|
+
insert into ${skins}
|
|
36
|
+
(id, submitter_id, display_name, description, tokens_json, status, rejection_code, rejection_reason, submitted_at)
|
|
37
|
+
values
|
|
38
|
+
(${id}, ${input.submitterId}, ${input.displayName}, ${description}, ${JSON.stringify(input.tokens)}, ${status}, ${rejectionCode}, ${rejectionReason}, ${submittedAt})`);
|
|
39
|
+
return {
|
|
40
|
+
id,
|
|
41
|
+
submitterId: input.submitterId,
|
|
42
|
+
displayName: input.displayName,
|
|
43
|
+
description,
|
|
44
|
+
status,
|
|
45
|
+
rejectionCode,
|
|
46
|
+
rejectionReason,
|
|
47
|
+
submittedAt,
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
async listAccepted() {
|
|
51
|
+
const result = await db.query(sql `
|
|
52
|
+
select id, submitter_id, display_name, description, status, rejection_code, rejection_reason, submitted_at
|
|
53
|
+
from ${skins} where status = 'accepted' order by submitted_at asc`);
|
|
54
|
+
return result.rows.map(toEntry);
|
|
55
|
+
},
|
|
56
|
+
async get(id) {
|
|
57
|
+
const result = await db.query(sql `
|
|
58
|
+
select id, submitter_id, display_name, description, status, rejection_code, rejection_reason, submitted_at
|
|
59
|
+
from ${skins} where id = ${id}`);
|
|
60
|
+
const row = result.rows[0];
|
|
61
|
+
return row === undefined ? null : toEntry(row);
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=skins.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skins.js","sourceRoot":"","sources":["../../src/registries/skins.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type DatabaseHandle } from '@cogenta/core';
|
|
2
|
+
export declare const REGISTRY_TABLES: {
|
|
3
|
+
readonly skins: 'cogenta_skin_gallery';
|
|
4
|
+
readonly skills: 'cogenta_skill_registry';
|
|
5
|
+
readonly themes: 'cogenta_theme_registry';
|
|
6
|
+
readonly plugins: 'cogenta_plugin_registry';
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Owned by `@cogenta/plugins`, following `permissions/tables.ts`'s
|
|
10
|
+
* `ensurePluginTables` pattern exactly: `create table if not exists`, run
|
|
11
|
+
* once at startup, no separate migration file.
|
|
12
|
+
*
|
|
13
|
+
* A skin submission never executes code (contract D: a skin is a closed JSON
|
|
14
|
+
* token set) — the "Skins" registry's only real gate is automatic validation
|
|
15
|
+
* against `@cogenta/render`'s `validateSkin`, so there is no `reviewed_by`/
|
|
16
|
+
* `reviewed_at` column the way a code-executing registry (plugins, themes)
|
|
17
|
+
* would need: `status` is written once, at submission time, by the gate
|
|
18
|
+
* itself, never by a human afterward.
|
|
19
|
+
*
|
|
20
|
+
* A skill submission is the opposite case in the lot's own "## Registres"
|
|
21
|
+
* table: "Revue de contenu", not automatic validation. `skill_name`/
|
|
22
|
+
* `skill_version`/`reviewed_by`/`reviewed_at` exist here — absent from the
|
|
23
|
+
* skins table above — because a skill's gate is a real two-step state
|
|
24
|
+
* machine (automatic parse pre-check, then a human decision), not a single
|
|
25
|
+
* automatic verdict.
|
|
26
|
+
*
|
|
27
|
+
* A theme submission (task 12) is automatic-only again, like skins — no
|
|
28
|
+
* `reviewed_by`/`reviewed_at` — so its base columns end up identical to the
|
|
29
|
+
* skins table's, minus `tokens_json` (a theme's real content lives on disk
|
|
30
|
+
* at submission time, verified in place, not persisted as an inline blob).
|
|
31
|
+
* With three real registries now built, this is the natural point to weigh a
|
|
32
|
+
* shared `Registry<T>` helper (task 11 deferred that exact question here) —
|
|
33
|
+
* the honest call: the three literal `create table` blocks below stay
|
|
34
|
+
* because Zig-zagging column sets (skins/themes share a shape skills does
|
|
35
|
+
* not; skills' own two-step state machine has no equivalent in the other
|
|
36
|
+
* two) make a generic helper either leaky (an interface wide enough for all
|
|
37
|
+
* three degenerates into passing raw column lists, which is what this
|
|
38
|
+
* function already does) or a false abstraction over accidentally-similar
|
|
39
|
+
* shapes. `identifier`/`sql`/`createIndexIfMissing` below already are the
|
|
40
|
+
* real shared primitives; duplicating eight lines of column declarations
|
|
41
|
+
* three times costs less than a parameterised table builder would.
|
|
42
|
+
*/
|
|
43
|
+
export declare function ensureRegistryTables(db: DatabaseHandle): Promise<void>;
|
|
44
|
+
//# sourceMappingURL=tables.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../src/registries/tables.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,cAAc,EAKpB,MAAM,eAAe,CAAA;AAEtB,eAAO,MAAM,eAAe;aAC1B,KAAK,EAAE,sBAAsB;aAC7B,MAAM,EAAE,wBAAwB;aAChC,MAAM,EAAE,wBAAwB;aAChC,OAAO,EAAE,yBAAyB;CAC1B,CAAA;AAMV;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAsB,oBAAoB,CAAC,EAAE,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAyF5E"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { identifier, sql, unsafeRaw, } from '@cogenta/core';
|
|
2
|
+
export const REGISTRY_TABLES = {
|
|
3
|
+
skins: 'cogenta_skin_gallery',
|
|
4
|
+
skills: 'cogenta_skill_registry',
|
|
5
|
+
themes: 'cogenta_theme_registry',
|
|
6
|
+
plugins: 'cogenta_plugin_registry',
|
|
7
|
+
};
|
|
8
|
+
function textColumn(dialect, length) {
|
|
9
|
+
return unsafeRaw(dialect === 'sqlite' ? 'text' : `varchar(${length})`);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Owned by `@cogenta/plugins`, following `permissions/tables.ts`'s
|
|
13
|
+
* `ensurePluginTables` pattern exactly: `create table if not exists`, run
|
|
14
|
+
* once at startup, no separate migration file.
|
|
15
|
+
*
|
|
16
|
+
* A skin submission never executes code (contract D: a skin is a closed JSON
|
|
17
|
+
* token set) — the "Skins" registry's only real gate is automatic validation
|
|
18
|
+
* against `@cogenta/render`'s `validateSkin`, so there is no `reviewed_by`/
|
|
19
|
+
* `reviewed_at` column the way a code-executing registry (plugins, themes)
|
|
20
|
+
* would need: `status` is written once, at submission time, by the gate
|
|
21
|
+
* itself, never by a human afterward.
|
|
22
|
+
*
|
|
23
|
+
* A skill submission is the opposite case in the lot's own "## Registres"
|
|
24
|
+
* table: "Revue de contenu", not automatic validation. `skill_name`/
|
|
25
|
+
* `skill_version`/`reviewed_by`/`reviewed_at` exist here — absent from the
|
|
26
|
+
* skins table above — because a skill's gate is a real two-step state
|
|
27
|
+
* machine (automatic parse pre-check, then a human decision), not a single
|
|
28
|
+
* automatic verdict.
|
|
29
|
+
*
|
|
30
|
+
* A theme submission (task 12) is automatic-only again, like skins — no
|
|
31
|
+
* `reviewed_by`/`reviewed_at` — so its base columns end up identical to the
|
|
32
|
+
* skins table's, minus `tokens_json` (a theme's real content lives on disk
|
|
33
|
+
* at submission time, verified in place, not persisted as an inline blob).
|
|
34
|
+
* With three real registries now built, this is the natural point to weigh a
|
|
35
|
+
* shared `Registry<T>` helper (task 11 deferred that exact question here) —
|
|
36
|
+
* the honest call: the three literal `create table` blocks below stay
|
|
37
|
+
* because Zig-zagging column sets (skins/themes share a shape skills does
|
|
38
|
+
* not; skills' own two-step state machine has no equivalent in the other
|
|
39
|
+
* two) make a generic helper either leaky (an interface wide enough for all
|
|
40
|
+
* three degenerates into passing raw column lists, which is what this
|
|
41
|
+
* function already does) or a false abstraction over accidentally-similar
|
|
42
|
+
* shapes. `identifier`/`sql`/`createIndexIfMissing` below already are the
|
|
43
|
+
* real shared primitives; duplicating eight lines of column declarations
|
|
44
|
+
* three times costs less than a parameterised table builder would.
|
|
45
|
+
*/
|
|
46
|
+
export async function ensureRegistryTables(db) {
|
|
47
|
+
const d = db.dialect;
|
|
48
|
+
const skins = identifier(REGISTRY_TABLES.skins, d);
|
|
49
|
+
const t255 = textColumn(d, 255);
|
|
50
|
+
const tLong = unsafeRaw(d === 'sqlite' ? 'text' : 'text');
|
|
51
|
+
await db.query(sql `
|
|
52
|
+
create table if not exists ${skins} (
|
|
53
|
+
id ${t255} not null primary key,
|
|
54
|
+
submitter_id ${t255} not null,
|
|
55
|
+
display_name ${t255} not null,
|
|
56
|
+
description ${tLong},
|
|
57
|
+
tokens_json ${tLong} not null,
|
|
58
|
+
status ${t255} not null,
|
|
59
|
+
rejection_code ${t255},
|
|
60
|
+
rejection_reason ${tLong},
|
|
61
|
+
submitted_at ${t255} not null
|
|
62
|
+
)`);
|
|
63
|
+
await createIndexIfMissing(db, 'cogenta_skin_gallery_status', skins, sql `(status, submitted_at)`);
|
|
64
|
+
const skills = identifier(REGISTRY_TABLES.skills, d);
|
|
65
|
+
await db.query(sql `
|
|
66
|
+
create table if not exists ${skills} (
|
|
67
|
+
id ${t255} not null primary key,
|
|
68
|
+
submitter_id ${t255} not null,
|
|
69
|
+
display_name ${t255} not null,
|
|
70
|
+
description ${tLong},
|
|
71
|
+
raw_content ${tLong} not null,
|
|
72
|
+
skill_name ${t255},
|
|
73
|
+
skill_version ${t255},
|
|
74
|
+
status ${t255} not null,
|
|
75
|
+
rejection_code ${t255},
|
|
76
|
+
rejection_reason ${tLong},
|
|
77
|
+
reviewed_by ${t255},
|
|
78
|
+
reviewed_at ${t255},
|
|
79
|
+
submitted_at ${t255} not null
|
|
80
|
+
)`);
|
|
81
|
+
await createIndexIfMissing(db, 'cogenta_skill_registry_status', skills, sql `(status, submitted_at)`);
|
|
82
|
+
const themes = identifier(REGISTRY_TABLES.themes, d);
|
|
83
|
+
await db.query(sql `
|
|
84
|
+
create table if not exists ${themes} (
|
|
85
|
+
id ${t255} not null primary key,
|
|
86
|
+
submitter_id ${t255} not null,
|
|
87
|
+
display_name ${t255} not null,
|
|
88
|
+
description ${tLong},
|
|
89
|
+
status ${t255} not null,
|
|
90
|
+
rejection_code ${t255},
|
|
91
|
+
rejection_reason ${tLong},
|
|
92
|
+
submitted_at ${t255} not null
|
|
93
|
+
)`);
|
|
94
|
+
await createIndexIfMissing(db, 'cogenta_theme_registry_status', themes, sql `(status, submitted_at)`);
|
|
95
|
+
const plugins = identifier(REGISTRY_TABLES.plugins, d);
|
|
96
|
+
await db.query(sql `
|
|
97
|
+
create table if not exists ${plugins} (
|
|
98
|
+
id ${t255} not null primary key,
|
|
99
|
+
submitter_id ${t255} not null,
|
|
100
|
+
display_name ${t255} not null,
|
|
101
|
+
description ${tLong},
|
|
102
|
+
plugin_name ${t255},
|
|
103
|
+
plugin_version ${t255},
|
|
104
|
+
status ${t255} not null,
|
|
105
|
+
rejection_code ${t255},
|
|
106
|
+
rejection_reason ${tLong},
|
|
107
|
+
reviewed_by ${t255},
|
|
108
|
+
reviewed_at ${t255},
|
|
109
|
+
submitted_at ${t255} not null
|
|
110
|
+
)`);
|
|
111
|
+
await createIndexIfMissing(db, 'cogenta_plugin_registry_status', plugins, sql `(status, submitted_at)`);
|
|
112
|
+
}
|
|
113
|
+
async function createIndexIfMissing(db, name, table, columns) {
|
|
114
|
+
await db
|
|
115
|
+
.query(sql `create index ${identifier(name, db.dialect)} on ${table} ${columns}`)
|
|
116
|
+
.catch(() => undefined); // already there — no portable "if not exists" for indexes
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=tables.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tables.js","sourceRoot":"","sources":["../../src/registries/tables.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type DatabaseHandle } from '@cogenta/core';
|
|
2
|
+
export type ThemeSubmissionStatus = 'accepted' | 'rejected';
|
|
3
|
+
export interface ThemeSubmissionInput {
|
|
4
|
+
readonly submitterId: string;
|
|
5
|
+
readonly displayName: string;
|
|
6
|
+
readonly description?: string;
|
|
7
|
+
/** Raw manifest input, unvalidated until `submit()` runs it through `parseThemeManifest`. */
|
|
8
|
+
readonly manifest: unknown;
|
|
9
|
+
/**
|
|
10
|
+
* Real filesystem path to the theme's extracted source tree — `verifyTheme`
|
|
11
|
+
* (contract D's install-time check) scans real files for the forbidden-import
|
|
12
|
+
* and `implements`-coverage rules, so a submission needs real content on
|
|
13
|
+
* disk, not just an inline JSON blob the way a skin submission does.
|
|
14
|
+
*/
|
|
15
|
+
readonly themeRoot: string;
|
|
16
|
+
/** Base64 Ed25519 signature over the manifest's canonical content, or `null` for none. */
|
|
17
|
+
readonly signatureBase64: string | null;
|
|
18
|
+
}
|
|
19
|
+
export interface ThemeRegistryEntry {
|
|
20
|
+
readonly id: string;
|
|
21
|
+
readonly submitterId: string;
|
|
22
|
+
readonly displayName: string;
|
|
23
|
+
readonly description: string | null;
|
|
24
|
+
readonly status: ThemeSubmissionStatus;
|
|
25
|
+
/** Which real gate failed: `signature`, or a contract-D `CogentaError` code — `null` when accepted. */
|
|
26
|
+
readonly rejectionCode: string | null;
|
|
27
|
+
readonly rejectionReason: string | null;
|
|
28
|
+
readonly submittedAt: string;
|
|
29
|
+
}
|
|
30
|
+
export interface ThemeRegistryOptions {
|
|
31
|
+
readonly trustedPublicKeys?: readonly string[];
|
|
32
|
+
}
|
|
33
|
+
export interface ThemeRegistry {
|
|
34
|
+
/**
|
|
35
|
+
* Runs the submission through the lot's own two named gates, in order:
|
|
36
|
+
* (1) **Signature** — the manifest's canonical content must carry a valid
|
|
37
|
+
* Ed25519 signature from a trusted registry key (task 9's real primitive,
|
|
38
|
+
* generalized to arbitrary content). Checked first because it is the more
|
|
39
|
+
* fundamental trust question — an unsigned theme is refused before any
|
|
40
|
+
* contract inspection runs, the same way an unlinked channel identity is
|
|
41
|
+
* refused before a command is even looked up (L6 task 3's precedent).
|
|
42
|
+
* (2) **Contrat vérifié** — contract D's real install-time check
|
|
43
|
+
* (`verifyTheme`, `@cogenta/render`): every vocabulary block is declared in
|
|
44
|
+
* `implements`, no forbidden import (`node:fs`, `@cogenta/core`, ...)
|
|
45
|
+
* appears anywhere in the theme's real sources, and its default skin
|
|
46
|
+
* (`tokens.json`) passes `validateSkin` — contract D's token rules, reused
|
|
47
|
+
* exactly as the Skins gallery (task 10) reused them, not reimplemented.
|
|
48
|
+
*
|
|
49
|
+
* No human-review step exists for themes ("Signature, contrat vérifié" is
|
|
50
|
+
* the lot's whole named requirement — no "revue" column value, unlike
|
|
51
|
+
* Skills) — a single automatic decision at submission time, same shape as
|
|
52
|
+
* the fully-automatic Skins gallery, just with more real checks.
|
|
53
|
+
*/
|
|
54
|
+
submit(input: ThemeSubmissionInput): Promise<ThemeRegistryEntry>;
|
|
55
|
+
listAccepted(): Promise<readonly ThemeRegistryEntry[]>;
|
|
56
|
+
get(id: string): Promise<ThemeRegistryEntry | null>;
|
|
57
|
+
}
|
|
58
|
+
export declare function createThemeRegistry(db: DatabaseHandle, options?: ThemeRegistryOptions, now?: () => number): ThemeRegistry;
|
|
59
|
+
//# sourceMappingURL=themes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"themes.d.ts","sourceRoot":"","sources":["../../src/registries/themes.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,cAAc,EAA0B,MAAM,eAAe,CAAA;AAKzF,MAAM,MAAM,qBAAqB,GAAG,UAAU,GAAG,UAAU,CAAA;AAE3D,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,6FAA6F;IAC7F,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,0FAA0F;IAC1F,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;CACxC;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IACnC,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAA;IACtC,uGAAuG;IACvG,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IACrC,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAC7B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAC/C;AAED,MAAM,WAAW,aAAa;IAC5B;;;;;;;;;;;;;;;;;;;OAmBG;IACH,MAAM,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAA;IAChE,YAAY,IAAI,OAAO,CAAC,SAAS,kBAAkB,EAAE,CAAC,CAAA;IACtD,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAA;CACpD;AA0BD,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,cAAc,EAClB,OAAO,GAAE,oBAAyB,EAClC,GAAG,GAAE,MAAM,MAAiB,GAC3B,aAAa,CA2Ef"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { CogentaError, identifier, newId, sql } from '@cogenta/core';
|
|
4
|
+
import { parseThemeManifest, validateSkin, verifyTheme } from '@cogenta/render';
|
|
5
|
+
import { TRUSTED_REGISTRY_PUBLIC_KEYS, verifyContentAgainstTrustedKeys } from '../signing/verify.js';
|
|
6
|
+
import { REGISTRY_TABLES } from './tables.js';
|
|
7
|
+
function toEntry(row) {
|
|
8
|
+
return {
|
|
9
|
+
id: row.id,
|
|
10
|
+
submitterId: row.submitter_id,
|
|
11
|
+
displayName: row.display_name,
|
|
12
|
+
description: row.description,
|
|
13
|
+
status: row.status === 'accepted' ? 'accepted' : 'rejected',
|
|
14
|
+
rejectionCode: row.rejection_code,
|
|
15
|
+
rejectionReason: row.rejection_reason,
|
|
16
|
+
submittedAt: row.submitted_at,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function createThemeRegistry(db, options = {}, now = Date.now) {
|
|
20
|
+
const themes = identifier(REGISTRY_TABLES.themes, db.dialect);
|
|
21
|
+
const trustedPublicKeys = options.trustedPublicKeys ?? TRUSTED_REGISTRY_PUBLIC_KEYS;
|
|
22
|
+
return {
|
|
23
|
+
async submit(input) {
|
|
24
|
+
const id = newId(now);
|
|
25
|
+
const submittedAt = new Date(now()).toISOString();
|
|
26
|
+
let status = 'accepted';
|
|
27
|
+
let rejectionCode = null;
|
|
28
|
+
let rejectionReason = null;
|
|
29
|
+
try {
|
|
30
|
+
const manifest = parseThemeManifest(input.manifest, input.displayName);
|
|
31
|
+
const signed = verifyContentAgainstTrustedKeys(manifest, input.signatureBase64, trustedPublicKeys);
|
|
32
|
+
if (!signed) {
|
|
33
|
+
throw new CogentaError({
|
|
34
|
+
code: 'THEME_SIGNATURE_INVALID',
|
|
35
|
+
message: `The theme "${manifest.name}" has no valid signature from a trusted registry key.`,
|
|
36
|
+
hint: 'Sign the theme manifest with a registered registry key before submitting, or trust the signing key explicitly.',
|
|
37
|
+
details: { theme: manifest.name },
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
await verifyTheme({ root: input.themeRoot, manifest });
|
|
41
|
+
const tokensPath = join(input.themeRoot, manifest.tokens);
|
|
42
|
+
const tokensRaw = await readFile(tokensPath, 'utf8');
|
|
43
|
+
validateSkin(JSON.parse(tokensRaw));
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
status = 'rejected';
|
|
47
|
+
rejectionCode = error instanceof CogentaError ? error.code : 'THEME_SUBMISSION_INVALID';
|
|
48
|
+
rejectionReason = error instanceof Error ? error.message : String(error);
|
|
49
|
+
}
|
|
50
|
+
const description = input.description ?? null;
|
|
51
|
+
await db.query(sql `
|
|
52
|
+
insert into ${themes}
|
|
53
|
+
(id, submitter_id, display_name, description, status, rejection_code, rejection_reason, submitted_at)
|
|
54
|
+
values
|
|
55
|
+
(${id}, ${input.submitterId}, ${input.displayName}, ${description}, ${status}, ${rejectionCode}, ${rejectionReason}, ${submittedAt})`);
|
|
56
|
+
return {
|
|
57
|
+
id,
|
|
58
|
+
submitterId: input.submitterId,
|
|
59
|
+
displayName: input.displayName,
|
|
60
|
+
description,
|
|
61
|
+
status,
|
|
62
|
+
rejectionCode,
|
|
63
|
+
rejectionReason,
|
|
64
|
+
submittedAt,
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
async listAccepted() {
|
|
68
|
+
const result = await db.query(sql `
|
|
69
|
+
select id, submitter_id, display_name, description, status, rejection_code, rejection_reason, submitted_at
|
|
70
|
+
from ${themes} where status = 'accepted' order by submitted_at asc`);
|
|
71
|
+
return result.rows.map(toEntry);
|
|
72
|
+
},
|
|
73
|
+
async get(id) {
|
|
74
|
+
const result = await db.query(sql `
|
|
75
|
+
select id, submitter_id, display_name, description, status, rejection_code, rejection_reason, submitted_at
|
|
76
|
+
from ${themes} where id = ${id}`);
|
|
77
|
+
const row = result.rows[0];
|
|
78
|
+
return row === undefined ? null : toEntry(row);
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=themes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"themes.js","sourceRoot":"","sources":["../../src/registries/themes.ts"],"names":[],"mappings":"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"}
|
package/dist/semver.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A small, real semver-range matcher scoped to exactly the range shapes
|
|
3
|
+
* `manifest.ts`'s `SEMVER_RANGE_PATTERN` already accepts: `^1.2.3`,
|
|
4
|
+
* `~1.2.3`, a bare exact `1.2.3`, and a space-separated list of comparators
|
|
5
|
+
* (`>=1.0.0 <2.0.0`). No `semver` dependency (R9) — this is the whole
|
|
6
|
+
* matcher a plugin's `engine` field realistically needs, not a general
|
|
7
|
+
* semver library.
|
|
8
|
+
*/
|
|
9
|
+
export interface SemverVersion {
|
|
10
|
+
readonly major: number;
|
|
11
|
+
readonly minor: number;
|
|
12
|
+
readonly patch: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function parseVersion(input: string): SemverVersion | null;
|
|
15
|
+
/** -1 if `a < b`, 0 if equal, 1 if `a > b`. */
|
|
16
|
+
export declare function compareVersions(a: SemverVersion, b: SemverVersion): -1 | 0 | 1;
|
|
17
|
+
/**
|
|
18
|
+
* Whether `versionString` satisfies `range`. Returns `false` (never throws)
|
|
19
|
+
* for a range or version this matcher cannot parse — an unparseable engine
|
|
20
|
+
* range already fails `manifest.ts`'s own validation before this is ever
|
|
21
|
+
* called, so this is a defensive fallback, not the primary guard.
|
|
22
|
+
*/
|
|
23
|
+
export declare function satisfiesRange(versionString: string, range: string): boolean;
|
|
24
|
+
//# sourceMappingURL=semver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semver.d.ts","sourceRoot":"","sources":["../src/semver.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;AAID,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAKhE;AAED,+CAA+C;AAC/C,wBAAgB,eAAe,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,aAAa,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAK9E;AA0CD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAiB5E"}
|