@litfamily/litcodex 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -21
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +2 -38
- package/dist/install/generated-skill-payload-hashes.d.ts +1 -6
- package/dist/install/generated-skill-payload-hashes.js +1 -6
- package/dist/install/skill-catalog.d.ts +1 -1
- package/dist/install/skill-catalog.js +1 -6
- package/dist/install/skill-resource-hashes.d.ts +1 -1
- package/dist/install/skill-resource-hashes.js +1 -1
- package/marketplace/plugins/litcodex/.codex-plugin/plugin.json +1 -1
- package/marketplace/plugins/litcodex/components/auto-update/package.json +1 -1
- package/marketplace/plugins/litcodex/components/comment-checker/package.json +1 -1
- package/marketplace/plugins/litcodex/components/git-bash/package.json +1 -1
- package/marketplace/plugins/litcodex/components/lit-loop/dist/cli.js +3 -20
- package/marketplace/plugins/litcodex/components/lit-loop/dist/codex-hook.js +1 -8
- package/marketplace/plugins/litcodex/components/lit-loop/dist/hook-cli.d.ts +2 -1
- package/marketplace/plugins/litcodex/components/lit-loop/dist/hook-cli.js +4 -27
- package/marketplace/plugins/litcodex/components/lit-loop/dist/markers.d.ts +0 -2
- package/marketplace/plugins/litcodex/components/lit-loop/dist/markers.js +0 -2
- package/marketplace/plugins/litcodex/components/lit-loop/dist/modes.d.ts +1 -3
- package/marketplace/plugins/litcodex/components/lit-loop/dist/modes.js +1 -10
- package/marketplace/plugins/litcodex/components/lit-loop/package.json +1 -1
- package/marketplace/plugins/litcodex/components/lsp/package.json +1 -1
- package/marketplace/plugins/litcodex/components/rules/dist/static-injection.js +2 -2
- package/marketplace/plugins/litcodex/components/rules/package.json +1 -1
- package/marketplace/plugins/litcodex/components/start-work-continuation/package.json +1 -1
- package/marketplace/plugins/litcodex/components/telemetry/package.json +1 -1
- package/marketplace/plugins/litcodex/components/wikify-knowledge/package.json +1 -1
- package/marketplace/plugins/litcodex/skills/browser-drive/scripts/capability-probe.mjs +13 -5
- package/node_modules/@litcodex/lit-loop/README.md +1 -7
- package/node_modules/@litcodex/lit-loop/dist/cli.js +3 -20
- package/node_modules/@litcodex/lit-loop/dist/codex-hook.js +1 -8
- package/node_modules/@litcodex/lit-loop/dist/hook-cli.d.ts +2 -1
- package/node_modules/@litcodex/lit-loop/dist/hook-cli.js +4 -27
- package/node_modules/@litcodex/lit-loop/dist/markers.d.ts +0 -2
- package/node_modules/@litcodex/lit-loop/dist/markers.js +0 -2
- package/node_modules/@litcodex/lit-loop/dist/modes.d.ts +1 -3
- package/node_modules/@litcodex/lit-loop/dist/modes.js +1 -10
- package/node_modules/@litcodex/lit-loop/package.json +1 -1
- package/node_modules/@litcodex/lit-loop/skills/browser-drive/scripts/capability-probe.mjs +13 -5
- package/package.json +2 -2
- package/readme-assets/badge-version.svg +1 -1
- package/readme-assets/cover-motion.webp +0 -0
- package/marketplace/plugins/litcodex/components/lit-loop/directives/skill-observer.md +0 -105
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/apply.d.ts +0 -16
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/apply.js +0 -227
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/config.d.ts +0 -13
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/config.js +0 -12
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/curator.d.ts +0 -14
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/curator.js +0 -112
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/hook.d.ts +0 -19
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/hook.js +0 -435
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/ownership.d.ts +0 -6
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/ownership.js +0 -145
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/paths.d.ts +0 -18
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/paths.js +0 -43
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/review.d.ts +0 -28
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/review.js +0 -702
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/store.d.ts +0 -32
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/store.js +0 -667
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/transaction.d.ts +0 -26
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/transaction.js +0 -524
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/types.d.ts +0 -81
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/types.js +0 -246
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/usage.d.ts +0 -6
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/usage.js +0 -119
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop-cli.d.ts +0 -8
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop-cli.js +0 -111
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-observer-cli.d.ts +0 -3
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-observer-cli.js +0 -116
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-observer-route.d.ts +0 -6
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-observer-route.js +0 -12
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-observer.d.ts +0 -18
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-observer.js +0 -946
- package/marketplace/plugins/litcodex/skills/skill-observer/SKILL.md +0 -215
- package/marketplace/plugins/litcodex/skills/skill-observer/agents/openai.yaml +0 -6
- package/marketplace/plugins/litcodex/skills/skill-observer/references/review-contract.md +0 -77
- package/node_modules/@litcodex/lit-loop/directives/skill-observer.md +0 -105
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/apply.d.ts +0 -16
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/apply.js +0 -227
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/config.d.ts +0 -13
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/config.js +0 -12
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/curator.d.ts +0 -14
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/curator.js +0 -112
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/hook.d.ts +0 -19
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/hook.js +0 -435
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/ownership.d.ts +0 -6
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/ownership.js +0 -145
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/paths.d.ts +0 -18
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/paths.js +0 -43
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/review.d.ts +0 -28
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/review.js +0 -702
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/store.d.ts +0 -32
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/store.js +0 -667
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/transaction.d.ts +0 -26
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/transaction.js +0 -524
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/types.d.ts +0 -81
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/types.js +0 -246
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/usage.d.ts +0 -6
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/usage.js +0 -119
- package/node_modules/@litcodex/lit-loop/dist/skill-loop-cli.d.ts +0 -8
- package/node_modules/@litcodex/lit-loop/dist/skill-loop-cli.js +0 -111
- package/node_modules/@litcodex/lit-loop/dist/skill-observer-cli.d.ts +0 -3
- package/node_modules/@litcodex/lit-loop/dist/skill-observer-cli.js +0 -116
- package/node_modules/@litcodex/lit-loop/dist/skill-observer-route.d.ts +0 -6
- package/node_modules/@litcodex/lit-loop/dist/skill-observer-route.js +0 -12
- package/node_modules/@litcodex/lit-loop/dist/skill-observer.d.ts +0 -18
- package/node_modules/@litcodex/lit-loop/dist/skill-observer.js +0 -946
- package/node_modules/@litcodex/lit-loop/skills/skill-observer/SKILL.md +0 -215
- package/node_modules/@litcodex/lit-loop/skills/skill-observer/references/review-contract.md +0 -77
|
@@ -1,524 +0,0 @@
|
|
|
1
|
-
import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
|
-
import { closeSync, constants, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
3
|
-
import { dirname, join, resolve } from "node:path";
|
|
4
|
-
import { snapshotIsAgentOwned } from "./ownership.js";
|
|
5
|
-
import { skillLoopPaths } from "./paths.js";
|
|
6
|
-
import { appendLedgerEntry, assertAgentMutationLockHeld, assertTargetNotPinned, atomicWrite, ensureAgentRoot, fsyncDirectory, readLedger, readProposalRecord, readUsage, replaceSkillWithSnapshot, snapshotDirectory, snapshotSkill, snapshotsEqual, validateLedgerEntry, validateSnapshotBlobs, withAgentMutationLock, writeProposalRecord, writeSnapshotToDirectory, writeUsage, } from "./store.js";
|
|
7
|
-
import { SkillLoopError, validateProposal, validateRelativeFile } from "./types.js";
|
|
8
|
-
import { nextAppliedUsageFrom, validateUsageSidecar } from "./usage.js";
|
|
9
|
-
const JOURNAL_KEYS = new Set([
|
|
10
|
-
"schema",
|
|
11
|
-
"operation",
|
|
12
|
-
"ledger",
|
|
13
|
-
"authorization",
|
|
14
|
-
"proposalBefore",
|
|
15
|
-
"proposalAfter",
|
|
16
|
-
"usageBefore",
|
|
17
|
-
"usageAfter",
|
|
18
|
-
"copies",
|
|
19
|
-
]);
|
|
20
|
-
const COPY_KEYS = new Set(["kind", "relativePath"]);
|
|
21
|
-
function isRecord(value) {
|
|
22
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23
|
-
}
|
|
24
|
-
function sameBytes(left, right) {
|
|
25
|
-
return JSON.stringify(left) === JSON.stringify(right);
|
|
26
|
-
}
|
|
27
|
-
function validateCopies(value, ledger) {
|
|
28
|
-
if (!Array.isArray(value) || value.length > 2) {
|
|
29
|
-
throw new SkillLoopError("TRANSACTION_INVALID", "the skill transaction copies are invalid");
|
|
30
|
-
}
|
|
31
|
-
const copies = value.map((candidate) => {
|
|
32
|
-
if (!isRecord(candidate) || Object.keys(candidate).some((key) => !COPY_KEYS.has(key))) {
|
|
33
|
-
throw new SkillLoopError("TRANSACTION_INVALID", "a skill transaction copy is invalid");
|
|
34
|
-
}
|
|
35
|
-
const kind = candidate["kind"];
|
|
36
|
-
const relativePath = candidate["relativePath"];
|
|
37
|
-
if (!(kind === "backup" || kind === "archive") ||
|
|
38
|
-
typeof relativePath !== "string" ||
|
|
39
|
-
validateRelativeFile(relativePath, "__transaction_copy__") !== relativePath) {
|
|
40
|
-
throw new SkillLoopError("TRANSACTION_INVALID", "a skill transaction copy is invalid");
|
|
41
|
-
}
|
|
42
|
-
const segments = relativePath.split("/");
|
|
43
|
-
if (segments.length !== 2 ||
|
|
44
|
-
(kind === "backup" ? segments[1] !== ledger.targetSkill : segments[0] !== ledger.targetSkill)) {
|
|
45
|
-
throw new SkillLoopError("TRANSACTION_INVALID", "a skill transaction copy target is invalid");
|
|
46
|
-
}
|
|
47
|
-
return { kind: kind, relativePath };
|
|
48
|
-
});
|
|
49
|
-
if (new Set(copies.map((copy) => copy.kind)).size !== copies.length) {
|
|
50
|
-
throw new SkillLoopError("TRANSACTION_INVALID", "the skill transaction copies are duplicated");
|
|
51
|
-
}
|
|
52
|
-
return copies;
|
|
53
|
-
}
|
|
54
|
-
function validateJournal(value, expectedRoot) {
|
|
55
|
-
if (!isRecord(value) || Object.keys(value).some((key) => !JOURNAL_KEYS.has(key))) {
|
|
56
|
-
throw new SkillLoopError("TRANSACTION_INVALID", "the skill transaction journal is invalid");
|
|
57
|
-
}
|
|
58
|
-
const ledger = validateLedgerEntry(value["ledger"]);
|
|
59
|
-
const authorization = value["authorization"];
|
|
60
|
-
const operation = value["operation"];
|
|
61
|
-
if (value["schema"] !== "litcodex.skill-transaction/v1" ||
|
|
62
|
-
!(operation === "apply" || operation === "rollback" || operation === "curator-transition") ||
|
|
63
|
-
ledger.operation !== operation ||
|
|
64
|
-
typeof authorization !== "string" ||
|
|
65
|
-
!/^[a-f0-9]{64}$/u.test(authorization) ||
|
|
66
|
-
resolve(ledger.targetRoot) !== resolve(expectedRoot)) {
|
|
67
|
-
throw new SkillLoopError("TRANSACTION_INVALID", "the skill transaction operation is invalid");
|
|
68
|
-
}
|
|
69
|
-
const copies = validateCopies(value["copies"], ledger);
|
|
70
|
-
const rawProposalBefore = value["proposalBefore"];
|
|
71
|
-
const proposalBefore = rawProposalBefore === undefined ? undefined : validateProposal(rawProposalBefore, expectedRoot);
|
|
72
|
-
const rawProposal = value["proposalAfter"];
|
|
73
|
-
const proposalAfter = rawProposal === undefined ? undefined : validateProposal(rawProposal, expectedRoot);
|
|
74
|
-
const rawUsage = value["usageAfter"];
|
|
75
|
-
const usageAfter = rawUsage === undefined ? undefined : validateUsageSidecar(rawUsage);
|
|
76
|
-
const rawUsageBefore = value["usageBefore"];
|
|
77
|
-
const usageBefore = rawUsageBefore === undefined || rawUsageBefore === null ? rawUsageBefore : validateUsageSidecar(rawUsageBefore);
|
|
78
|
-
if (operation === "apply") {
|
|
79
|
-
const expectedProposalAfter = proposalBefore === undefined
|
|
80
|
-
? undefined
|
|
81
|
-
: { ...proposalBefore, status: "applied", ledgerEntryId: ledger.id };
|
|
82
|
-
if (proposalBefore?.status !== "approved" ||
|
|
83
|
-
proposalBefore.id !== ledger.proposalId ||
|
|
84
|
-
proposalBefore.targetSkill !== ledger.targetSkill ||
|
|
85
|
-
usageBefore === undefined ||
|
|
86
|
-
proposalAfter?.status !== "applied" ||
|
|
87
|
-
proposalAfter.ledgerEntryId !== ledger.id ||
|
|
88
|
-
proposalAfter.id !== ledger.proposalId ||
|
|
89
|
-
proposalAfter.targetSkill !== ledger.targetSkill ||
|
|
90
|
-
!sameBytes(proposalAfter, expectedProposalAfter) ||
|
|
91
|
-
usageAfter?.updatedAt !== ledger.createdAt ||
|
|
92
|
-
usageAfter.skills[ledger.targetSkill]?.state !== "active" ||
|
|
93
|
-
copies.length !== 0) {
|
|
94
|
-
throw new SkillLoopError("TRANSACTION_INVALID", "the apply transaction metadata is inconsistent");
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
else if (operation === "rollback") {
|
|
98
|
-
const expectedProposalAfter = proposalBefore === undefined
|
|
99
|
-
? undefined
|
|
100
|
-
: { ...proposalBefore, status: "rolled-back", ledgerEntryId: ledger.id };
|
|
101
|
-
if (usageBefore === undefined ||
|
|
102
|
-
usageAfter !== undefined ||
|
|
103
|
-
copies.length !== 0 ||
|
|
104
|
-
(ledger.proposalId === null) !== (proposalAfter === undefined) ||
|
|
105
|
-
(proposalAfter === undefined) !== (proposalBefore === undefined) ||
|
|
106
|
-
!sameBytes(proposalAfter, expectedProposalAfter) ||
|
|
107
|
-
(proposalAfter !== undefined &&
|
|
108
|
-
(proposalAfter.status !== "rolled-back" ||
|
|
109
|
-
proposalAfter.ledgerEntryId !== ledger.id ||
|
|
110
|
-
proposalAfter.id !== ledger.proposalId ||
|
|
111
|
-
proposalAfter.targetSkill !== ledger.targetSkill ||
|
|
112
|
-
proposalBefore?.id !== proposalAfter.id ||
|
|
113
|
-
proposalBefore.targetSkill !== proposalAfter.targetSkill))) {
|
|
114
|
-
throw new SkillLoopError("TRANSACTION_INVALID", "the rollback transaction metadata is inconsistent");
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
const expectedState = ledger.after.length === 0 ? "archived" : "stale";
|
|
119
|
-
const kinds = new Set(copies.map((copy) => copy.kind));
|
|
120
|
-
if (proposalAfter !== undefined ||
|
|
121
|
-
proposalBefore !== undefined ||
|
|
122
|
-
usageBefore === undefined ||
|
|
123
|
-
usageAfter?.updatedAt !== ledger.createdAt ||
|
|
124
|
-
usageAfter.skills[ledger.targetSkill]?.state !== expectedState ||
|
|
125
|
-
!kinds.has("backup") ||
|
|
126
|
-
(expectedState === "archived" ? !kinds.has("archive") : kinds.has("archive"))) {
|
|
127
|
-
throw new SkillLoopError("TRANSACTION_INVALID", "the curator transaction metadata is inconsistent");
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return {
|
|
131
|
-
schema: "litcodex.skill-transaction/v1",
|
|
132
|
-
operation,
|
|
133
|
-
ledger,
|
|
134
|
-
authorization,
|
|
135
|
-
...(proposalBefore === undefined ? {} : { proposalBefore }),
|
|
136
|
-
...(proposalAfter === undefined ? {} : { proposalAfter }),
|
|
137
|
-
...(usageBefore === undefined ? {} : { usageBefore }),
|
|
138
|
-
...(usageAfter === undefined ? {} : { usageAfter }),
|
|
139
|
-
copies,
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
function authorityPath(agentRoot) {
|
|
143
|
-
return join(resolve(agentRoot, ".."), ".litcodex-skill-loop-authority");
|
|
144
|
-
}
|
|
145
|
-
function canonicalJson(value) {
|
|
146
|
-
if (Array.isArray(value))
|
|
147
|
-
return value.map(canonicalJson);
|
|
148
|
-
if (isRecord(value)) {
|
|
149
|
-
return Object.fromEntries(Object.keys(value)
|
|
150
|
-
.sort()
|
|
151
|
-
.map((key) => [key, canonicalJson(value[key])]));
|
|
152
|
-
}
|
|
153
|
-
return value;
|
|
154
|
-
}
|
|
155
|
-
function journalPayload(workspaceRoot, journal) {
|
|
156
|
-
return JSON.stringify(canonicalJson({ workspaceRoot: resolve(workspaceRoot), journal }));
|
|
157
|
-
}
|
|
158
|
-
function readAuthority(agentRoot) {
|
|
159
|
-
const path = authorityPath(agentRoot);
|
|
160
|
-
if (!existsSync(path))
|
|
161
|
-
return null;
|
|
162
|
-
const parent = lstatSync(dirname(path));
|
|
163
|
-
let stat = lstatSync(path);
|
|
164
|
-
const uid = typeof process.getuid === "function" ? process.getuid() : stat.uid;
|
|
165
|
-
if (parent.isSymbolicLink() ||
|
|
166
|
-
!parent.isDirectory() ||
|
|
167
|
-
(parent.mode & 0o022) !== 0 ||
|
|
168
|
-
(parent.uid !== uid && parent.uid !== 0) ||
|
|
169
|
-
stat.isSymbolicLink() ||
|
|
170
|
-
!stat.isFile() ||
|
|
171
|
-
(stat.mode & 0o777) !== 0o600 ||
|
|
172
|
-
(stat.uid !== uid && stat.uid !== 0) ||
|
|
173
|
-
stat.nlink !== 1) {
|
|
174
|
-
throw new SkillLoopError("TRANSACTION_UNAUTHORIZED", "the transaction authority is unsafe");
|
|
175
|
-
}
|
|
176
|
-
for (let attempt = 0; stat.size !== 32 && attempt < 20; attempt += 1) {
|
|
177
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5);
|
|
178
|
-
const next = lstatSync(path);
|
|
179
|
-
if (next.dev !== stat.dev ||
|
|
180
|
-
next.ino !== stat.ino ||
|
|
181
|
-
next.uid !== stat.uid ||
|
|
182
|
-
next.mode !== stat.mode ||
|
|
183
|
-
next.nlink !== stat.nlink ||
|
|
184
|
-
next.ctimeMs !== stat.ctimeMs ||
|
|
185
|
-
next.birthtimeMs !== stat.birthtimeMs) {
|
|
186
|
-
throw new SkillLoopError("TRANSACTION_UNAUTHORIZED", "the transaction authority changed during creation");
|
|
187
|
-
}
|
|
188
|
-
stat = next;
|
|
189
|
-
}
|
|
190
|
-
if (stat.size !== 32)
|
|
191
|
-
throw new SkillLoopError("TRANSACTION_UNAUTHORIZED", "the transaction authority is incomplete");
|
|
192
|
-
const key = readFileSync(path);
|
|
193
|
-
const after = lstatSync(path);
|
|
194
|
-
if (key.byteLength !== 32 ||
|
|
195
|
-
after.dev !== stat.dev ||
|
|
196
|
-
after.ino !== stat.ino ||
|
|
197
|
-
after.uid !== stat.uid ||
|
|
198
|
-
after.mode !== stat.mode ||
|
|
199
|
-
after.nlink !== stat.nlink ||
|
|
200
|
-
after.size !== stat.size ||
|
|
201
|
-
after.mtimeMs !== stat.mtimeMs ||
|
|
202
|
-
after.ctimeMs !== stat.ctimeMs ||
|
|
203
|
-
after.birthtimeMs !== stat.birthtimeMs)
|
|
204
|
-
throw new SkillLoopError("TRANSACTION_UNAUTHORIZED", "the transaction authority is invalid");
|
|
205
|
-
return key;
|
|
206
|
-
}
|
|
207
|
-
function ensureAuthority(agentRoot) {
|
|
208
|
-
const existing = readAuthority(agentRoot);
|
|
209
|
-
if (existing !== null)
|
|
210
|
-
return existing;
|
|
211
|
-
const key = randomBytes(32);
|
|
212
|
-
const path = authorityPath(agentRoot);
|
|
213
|
-
let descriptor;
|
|
214
|
-
try {
|
|
215
|
-
descriptor = openSync(path, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 0o600);
|
|
216
|
-
writeFileSync(descriptor, key);
|
|
217
|
-
fsyncSync(descriptor);
|
|
218
|
-
closeSync(descriptor);
|
|
219
|
-
descriptor = undefined;
|
|
220
|
-
fsyncDirectory(dirname(path));
|
|
221
|
-
return readAuthority(agentRoot);
|
|
222
|
-
}
|
|
223
|
-
catch (error) {
|
|
224
|
-
if (descriptor !== undefined)
|
|
225
|
-
closeSync(descriptor);
|
|
226
|
-
if (error.code === "EEXIST") {
|
|
227
|
-
const raced = readAuthority(agentRoot);
|
|
228
|
-
if (raced !== null)
|
|
229
|
-
return raced;
|
|
230
|
-
}
|
|
231
|
-
throw error instanceof SkillLoopError
|
|
232
|
-
? error
|
|
233
|
-
: new SkillLoopError("TRANSACTION_UNAUTHORIZED", "the transaction authority could not be created safely");
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
function quarantineJournal(paths, raw) {
|
|
237
|
-
const root = join(paths.stateRoot, "skill-transaction-quarantine");
|
|
238
|
-
if (!existsSync(root))
|
|
239
|
-
mkdirSync(root, { recursive: false, mode: 0o700 });
|
|
240
|
-
const stat = lstatSync(root);
|
|
241
|
-
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
242
|
-
throw new SkillLoopError("TRANSACTION_UNAUTHORIZED", "the transaction quarantine is unsafe");
|
|
243
|
-
}
|
|
244
|
-
const digest = createHash("sha256").update(raw).digest("hex");
|
|
245
|
-
const destination = join(root, `${digest}.json`);
|
|
246
|
-
if (existsSync(destination))
|
|
247
|
-
unlinkSync(paths.transactionPath);
|
|
248
|
-
else
|
|
249
|
-
renameSync(paths.transactionPath, destination);
|
|
250
|
-
fsyncDirectory(root);
|
|
251
|
-
fsyncDirectory(paths.stateRoot);
|
|
252
|
-
}
|
|
253
|
-
function authorizedJournal(workspaceRoot, raw, paths) {
|
|
254
|
-
let parsed;
|
|
255
|
-
try {
|
|
256
|
-
parsed = JSON.parse(raw.toString("utf8"));
|
|
257
|
-
}
|
|
258
|
-
catch {
|
|
259
|
-
quarantineJournal(paths, raw);
|
|
260
|
-
throw new SkillLoopError("TRANSACTION_UNAUTHORIZED", "the transaction journal is not authorized");
|
|
261
|
-
}
|
|
262
|
-
if (!isRecord(parsed) || typeof parsed["authorization"] !== "string") {
|
|
263
|
-
quarantineJournal(paths, raw);
|
|
264
|
-
throw new SkillLoopError("TRANSACTION_UNAUTHORIZED", "the transaction journal is not authorized");
|
|
265
|
-
}
|
|
266
|
-
let key;
|
|
267
|
-
try {
|
|
268
|
-
key = readAuthority(paths.agentRoot);
|
|
269
|
-
}
|
|
270
|
-
catch {
|
|
271
|
-
quarantineJournal(paths, raw);
|
|
272
|
-
throw new SkillLoopError("TRANSACTION_UNAUTHORIZED", "the transaction authority rejected the journal");
|
|
273
|
-
}
|
|
274
|
-
const authorization = parsed["authorization"];
|
|
275
|
-
const unsigned = { ...parsed };
|
|
276
|
-
delete unsigned["authorization"];
|
|
277
|
-
const expected = key === null
|
|
278
|
-
? ""
|
|
279
|
-
: createHmac("sha256", key)
|
|
280
|
-
.update(journalPayload(workspaceRoot, unsigned))
|
|
281
|
-
.digest("hex");
|
|
282
|
-
if (key === null ||
|
|
283
|
-
!/^[a-f0-9]{64}$/u.test(authorization) ||
|
|
284
|
-
!timingSafeEqual(Buffer.from(authorization), Buffer.from(expected))) {
|
|
285
|
-
quarantineJournal(paths, raw);
|
|
286
|
-
throw new SkillLoopError("TRANSACTION_UNAUTHORIZED", "the transaction journal is not authorized");
|
|
287
|
-
}
|
|
288
|
-
return parsed;
|
|
289
|
-
}
|
|
290
|
-
export function readMutationJournal(workspaceRoot, env) {
|
|
291
|
-
const paths = skillLoopPaths(workspaceRoot, env);
|
|
292
|
-
if (!existsSync(paths.transactionPath))
|
|
293
|
-
return null;
|
|
294
|
-
const stat = lstatSync(paths.transactionPath);
|
|
295
|
-
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
296
|
-
throw new SkillLoopError("TRANSACTION_INVALID", "the skill transaction journal is unsafe");
|
|
297
|
-
}
|
|
298
|
-
try {
|
|
299
|
-
const raw = readFileSync(paths.transactionPath);
|
|
300
|
-
return validateJournal(authorizedJournal(workspaceRoot, raw, paths), paths.agentRoot);
|
|
301
|
-
}
|
|
302
|
-
catch (error) {
|
|
303
|
-
if (error instanceof SkillLoopError)
|
|
304
|
-
throw error;
|
|
305
|
-
throw new SkillLoopError("TRANSACTION_INVALID", "the skill transaction journal is invalid JSON");
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
export function writeMutationJournal(workspaceRoot, journal, env) {
|
|
309
|
-
const paths = skillLoopPaths(workspaceRoot, env);
|
|
310
|
-
assertAgentMutationLockHeld(paths);
|
|
311
|
-
if (existsSync(paths.transactionPath)) {
|
|
312
|
-
throw new SkillLoopError("TRANSACTION_BUSY", "a prior skill transaction requires recovery");
|
|
313
|
-
}
|
|
314
|
-
ensureAgentRoot(paths);
|
|
315
|
-
const proposalBefore = journal.proposalAfter === undefined ? undefined : readProposalRecord(paths, journal.proposalAfter.id);
|
|
316
|
-
validateTerminalProposalBinding(workspaceRoot, env, proposalBefore);
|
|
317
|
-
const usageBefore = readUsage(paths);
|
|
318
|
-
let expectedUsageAfter;
|
|
319
|
-
if (journal.operation === "apply") {
|
|
320
|
-
expectedUsageAfter = nextAppliedUsageFrom(usageBefore, journal.ledger.targetSkill, journal.ledger.createdAt);
|
|
321
|
-
}
|
|
322
|
-
else if (journal.operation === "curator-transition") {
|
|
323
|
-
const sidecar = usageBefore === null ? null : validateUsageSidecar(usageBefore);
|
|
324
|
-
if (sidecar === null) {
|
|
325
|
-
throw new SkillLoopError("TRANSACTION_METADATA_CONFLICT", "the curator usage preimage is unavailable");
|
|
326
|
-
}
|
|
327
|
-
const entry = sidecar?.skills[journal.ledger.targetSkill];
|
|
328
|
-
const state = journal.ledger.after.length === 0 ? "archived" : "stale";
|
|
329
|
-
if (entry === undefined ||
|
|
330
|
-
entry.pinned ||
|
|
331
|
-
(state === "stale" ? entry.state !== "active" : entry.state !== "stale")) {
|
|
332
|
-
throw new SkillLoopError("TRANSACTION_METADATA_CONFLICT", "the curator usage preimage is no longer eligible");
|
|
333
|
-
}
|
|
334
|
-
expectedUsageAfter = {
|
|
335
|
-
schema: "litfamily.skill-usage/v1",
|
|
336
|
-
updatedAt: journal.ledger.createdAt,
|
|
337
|
-
skills: {
|
|
338
|
-
...sidecar.skills,
|
|
339
|
-
[journal.ledger.targetSkill]: {
|
|
340
|
-
...entry,
|
|
341
|
-
state,
|
|
342
|
-
archived_at: state === "archived" ? journal.ledger.createdAt : null,
|
|
343
|
-
},
|
|
344
|
-
},
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
if (JSON.stringify(journal.usageAfter) !== JSON.stringify(expectedUsageAfter)) {
|
|
348
|
-
throw new SkillLoopError("TRANSACTION_METADATA_CONFLICT", "the transaction usage postimage was stale");
|
|
349
|
-
}
|
|
350
|
-
const unsigned = {
|
|
351
|
-
...journal,
|
|
352
|
-
...(proposalBefore === undefined ? {} : { proposalBefore }),
|
|
353
|
-
usageBefore,
|
|
354
|
-
};
|
|
355
|
-
const key = ensureAuthority(paths.agentRoot);
|
|
356
|
-
const authorization = createHmac("sha256", key).update(journalPayload(workspaceRoot, unsigned)).digest("hex");
|
|
357
|
-
const validated = validateJournal({ ...unsigned, authorization }, paths.agentRoot);
|
|
358
|
-
validateSnapshotBlobs(paths, [...validated.ledger.before, ...validated.ledger.after]);
|
|
359
|
-
atomicWrite(paths.transactionPath, `${JSON.stringify(validated, null, 2)}\n`);
|
|
360
|
-
return validated;
|
|
361
|
-
}
|
|
362
|
-
function ensureSafeDirectory(path, label) {
|
|
363
|
-
if (!existsSync(path))
|
|
364
|
-
mkdirSync(path, { recursive: false, mode: 0o700 });
|
|
365
|
-
const stat = lstatSync(path);
|
|
366
|
-
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
367
|
-
throw new SkillLoopError("TRANSACTION_INVALID", `${label} is not a safe directory`);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
function ensureCopy(workspaceRoot, env, journal, copy) {
|
|
371
|
-
const paths = skillLoopPaths(workspaceRoot, env);
|
|
372
|
-
const root = copy.kind === "backup" ? paths.curatorBackupsRoot : paths.archiveRoot;
|
|
373
|
-
ensureSafeDirectory(root, `${copy.kind} root`);
|
|
374
|
-
const [parentName] = copy.relativePath.split("/");
|
|
375
|
-
if (parentName === undefined)
|
|
376
|
-
throw new SkillLoopError("TRANSACTION_INVALID", "the copy parent is invalid");
|
|
377
|
-
const parent = join(root, parentName);
|
|
378
|
-
ensureSafeDirectory(parent, `${copy.kind} parent`);
|
|
379
|
-
const destination = join(root, copy.relativePath);
|
|
380
|
-
const stage = `${destination}.litcodex-stage-${journal.authorization.slice(0, 16)}`;
|
|
381
|
-
if (!existsSync(destination)) {
|
|
382
|
-
if (existsSync(stage))
|
|
383
|
-
rmSync(stage, { recursive: true, force: true });
|
|
384
|
-
writeSnapshotToDirectory(paths, journal.ledger.before, stage);
|
|
385
|
-
if (!snapshotsEqual(snapshotDirectory(paths, stage), journal.ledger.before)) {
|
|
386
|
-
throw new SkillLoopError("TRANSACTION_COPY_MISMATCH", "a staged transaction snapshot copy mismatched");
|
|
387
|
-
}
|
|
388
|
-
renameSync(stage, destination);
|
|
389
|
-
fsyncDirectory(parent);
|
|
390
|
-
}
|
|
391
|
-
else if (existsSync(stage))
|
|
392
|
-
rmSync(stage, { recursive: true, force: true });
|
|
393
|
-
if (!snapshotsEqual(snapshotDirectory(paths, destination), journal.ledger.before)) {
|
|
394
|
-
throw new SkillLoopError("TRANSACTION_COPY_MISMATCH", "a transaction snapshot copy mismatched");
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
function validateTerminalProposalBinding(workspaceRoot, env, proposal) {
|
|
398
|
-
if (proposal === undefined || proposal.status === "pending" || proposal.status === "approved")
|
|
399
|
-
return;
|
|
400
|
-
const expectedOperation = proposal.status === "applied" ? "apply" : proposal.status === "rejected" ? "reject" : "rollback";
|
|
401
|
-
const entry = readLedger(workspaceRoot, { env }).find((candidate) => candidate.id === proposal.ledgerEntryId);
|
|
402
|
-
if (entry === undefined ||
|
|
403
|
-
entry.operation !== expectedOperation ||
|
|
404
|
-
entry.proposalId !== proposal.id ||
|
|
405
|
-
entry.targetSkill !== proposal.targetSkill ||
|
|
406
|
-
resolve(entry.targetRoot) !== resolve(proposal.targetRoot)) {
|
|
407
|
-
throw new SkillLoopError("TRANSACTION_REFERENCE_INVALID", "the terminal proposal ledger binding is invalid");
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
function validateMetadataPlacement(workspaceRoot, env, journal) {
|
|
411
|
-
const paths = skillLoopPaths(workspaceRoot, env);
|
|
412
|
-
validateTerminalProposalBinding(workspaceRoot, env, journal.proposalBefore);
|
|
413
|
-
if (journal.proposalBefore !== undefined) {
|
|
414
|
-
const current = readProposalRecord(paths, journal.proposalBefore.id);
|
|
415
|
-
if (!sameBytes(current, journal.proposalBefore) && !sameBytes(current, journal.proposalAfter)) {
|
|
416
|
-
throw new SkillLoopError("TRANSACTION_METADATA_CONFLICT", "the transaction proposal changed after sealing");
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
if (journal.usageBefore !== undefined) {
|
|
420
|
-
const current = readUsage(paths);
|
|
421
|
-
const after = journal.usageAfter === undefined ? journal.usageBefore : journal.usageAfter;
|
|
422
|
-
if (!sameBytes(current, journal.usageBefore) && !sameBytes(current, after)) {
|
|
423
|
-
throw new SkillLoopError("TRANSACTION_METADATA_CONFLICT", "the transaction usage changed after sealing");
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
assertTargetNotPinned(paths, journal.ledger.targetSkill);
|
|
427
|
-
}
|
|
428
|
-
function validateOwnership(paths, journal) {
|
|
429
|
-
const beforeOwned = journal.ledger.before.length > 0 && snapshotIsAgentOwned(paths, journal.ledger.before);
|
|
430
|
-
const afterOwned = journal.ledger.after.length > 0 && snapshotIsAgentOwned(paths, journal.ledger.after);
|
|
431
|
-
if (journal.operation === "apply") {
|
|
432
|
-
const create = journal.proposalBefore?.action === "create";
|
|
433
|
-
const archive = journal.proposalBefore?.action === "archive";
|
|
434
|
-
if ((create ? journal.ledger.before.length !== 0 : !beforeOwned) ||
|
|
435
|
-
(archive ? journal.ledger.after.length !== 0 : !afterOwned)) {
|
|
436
|
-
throw new SkillLoopError("TRANSACTION_REFERENCE_INVALID", "the apply ownership invariant is invalid");
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
else if (journal.operation === "curator-transition" &&
|
|
440
|
-
(!beforeOwned || (journal.ledger.after.length > 0 && !afterOwned))) {
|
|
441
|
-
throw new SkillLoopError("TRANSACTION_REFERENCE_INVALID", "the curator ownership invariant is invalid");
|
|
442
|
-
}
|
|
443
|
-
else if (journal.operation === "rollback") {
|
|
444
|
-
if ((journal.ledger.before.length > 0 && !beforeOwned) || (journal.ledger.after.length > 0 && !afterOwned)) {
|
|
445
|
-
throw new SkillLoopError("TRANSACTION_REFERENCE_INVALID", "the rollback ownership invariant is invalid");
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
function validateExpectation(journal, expected) {
|
|
450
|
-
if (journal.operation !== expected.operation ||
|
|
451
|
-
(expected.proposalId !== undefined && journal.ledger.proposalId !== expected.proposalId) ||
|
|
452
|
-
(expected.restoresLedgerEntryId !== undefined &&
|
|
453
|
-
journal.ledger.restoresLedgerEntryId !== expected.restoresLedgerEntryId)) {
|
|
454
|
-
throw new SkillLoopError("TRANSACTION_PENDING_MISMATCH", "a different authorized transaction requires recovery");
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
function ensureLedger(workspaceRoot, env, journal) {
|
|
458
|
-
const paths = skillLoopPaths(workspaceRoot, env);
|
|
459
|
-
const existing = readLedger(workspaceRoot, { env }).find((entry) => entry.id === journal.ledger.id);
|
|
460
|
-
if (existing === undefined)
|
|
461
|
-
appendLedgerEntry(paths, journal.ledger);
|
|
462
|
-
else if (JSON.stringify(existing) !== JSON.stringify(journal.ledger)) {
|
|
463
|
-
throw new SkillLoopError("TRANSACTION_LEDGER_MISMATCH", "the transaction ledger id has conflicting bytes");
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
function validateLedgerPlacement(workspaceRoot, env, journal) {
|
|
467
|
-
const entries = readLedger(workspaceRoot, { env });
|
|
468
|
-
const existing = entries.find((entry) => entry.id === journal.ledger.id);
|
|
469
|
-
if (existing !== undefined) {
|
|
470
|
-
if (JSON.stringify(existing) !== JSON.stringify(journal.ledger)) {
|
|
471
|
-
throw new SkillLoopError("TRANSACTION_LEDGER_MISMATCH", "the transaction ledger id has conflicting bytes");
|
|
472
|
-
}
|
|
473
|
-
return;
|
|
474
|
-
}
|
|
475
|
-
if (journal.operation !== "rollback")
|
|
476
|
-
return;
|
|
477
|
-
const source = entries.find((entry) => entry.id === journal.ledger.restoresLedgerEntryId);
|
|
478
|
-
if (source === undefined ||
|
|
479
|
-
source.operation === "reject" ||
|
|
480
|
-
source.targetSkill !== journal.ledger.targetSkill ||
|
|
481
|
-
resolve(source.targetRoot) !== resolve(journal.ledger.targetRoot) ||
|
|
482
|
-
!snapshotsEqual(journal.ledger.before, source.after) ||
|
|
483
|
-
!snapshotsEqual(journal.ledger.after, source.before)) {
|
|
484
|
-
throw new SkillLoopError("TRANSACTION_REFERENCE_INVALID", "the rollback transaction reference is invalid");
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
export function recoverMutationJournalWhileLocked(workspaceRoot, env, expected) {
|
|
488
|
-
const paths = skillLoopPaths(workspaceRoot, env);
|
|
489
|
-
assertAgentMutationLockHeld(paths);
|
|
490
|
-
const journal = readMutationJournal(workspaceRoot, env);
|
|
491
|
-
if (journal === null)
|
|
492
|
-
return null;
|
|
493
|
-
validateExpectation(journal, expected);
|
|
494
|
-
validateSnapshotBlobs(paths, [...journal.ledger.before, ...journal.ledger.after]);
|
|
495
|
-
validateLedgerPlacement(workspaceRoot, env, journal);
|
|
496
|
-
validateMetadataPlacement(workspaceRoot, env, journal);
|
|
497
|
-
validateOwnership(paths, journal);
|
|
498
|
-
for (const copy of journal.copies)
|
|
499
|
-
ensureCopy(workspaceRoot, env, journal, copy);
|
|
500
|
-
const current = snapshotSkill(paths, journal.ledger.targetSkill);
|
|
501
|
-
if (snapshotsEqual(current, journal.ledger.before)) {
|
|
502
|
-
replaceSkillWithSnapshot(paths, journal.ledger.targetSkill, journal.ledger.after);
|
|
503
|
-
}
|
|
504
|
-
else if (!snapshotsEqual(current, journal.ledger.after)) {
|
|
505
|
-
throw new SkillLoopError("TRANSACTION_CAS_MISMATCH", "the transaction target has conflicting bytes");
|
|
506
|
-
}
|
|
507
|
-
if (!snapshotsEqual(snapshotSkill(paths, journal.ledger.targetSkill), journal.ledger.after)) {
|
|
508
|
-
throw new SkillLoopError("TRANSACTION_VERIFY_FAILED", "the transaction postimage failed verification");
|
|
509
|
-
}
|
|
510
|
-
ensureLedger(workspaceRoot, env, journal);
|
|
511
|
-
if (journal.proposalAfter !== undefined)
|
|
512
|
-
writeProposalRecord(paths, journal.proposalAfter);
|
|
513
|
-
if (journal.usageAfter !== undefined)
|
|
514
|
-
writeUsage(paths, journal.usageAfter);
|
|
515
|
-
unlinkSync(paths.transactionPath);
|
|
516
|
-
fsyncDirectory(paths.stateRoot);
|
|
517
|
-
return journal;
|
|
518
|
-
}
|
|
519
|
-
export function recoverMutationJournal(workspaceRoot, env, expected) {
|
|
520
|
-
const paths = skillLoopPaths(workspaceRoot, env);
|
|
521
|
-
if (!existsSync(paths.transactionPath))
|
|
522
|
-
return null;
|
|
523
|
-
return withAgentMutationLock(paths, () => recoverMutationJournalWhileLocked(workspaceRoot, env, expected));
|
|
524
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
export type ProposalAction = "patch" | "create" | "add-reference" | "archive";
|
|
2
|
-
export type ProposalStatus = "pending" | "approved" | "applied" | "rejected" | "rolled-back";
|
|
3
|
-
export interface SkillProposalPatch {
|
|
4
|
-
readonly file: string;
|
|
5
|
-
readonly oldString: string;
|
|
6
|
-
readonly newString: string;
|
|
7
|
-
}
|
|
8
|
-
export interface SkillProposalCreateFile {
|
|
9
|
-
readonly file: string;
|
|
10
|
-
readonly content: string;
|
|
11
|
-
}
|
|
12
|
-
export interface SkillProposalCreateSpec {
|
|
13
|
-
readonly files: readonly SkillProposalCreateFile[];
|
|
14
|
-
}
|
|
15
|
-
export interface SkillProposal {
|
|
16
|
-
readonly schema: "litfamily.skill-proposal/v1";
|
|
17
|
-
readonly id: string;
|
|
18
|
-
readonly createdAt: string;
|
|
19
|
-
readonly host: "litcodex";
|
|
20
|
-
readonly sessionRef: string;
|
|
21
|
-
readonly signal: string;
|
|
22
|
-
readonly targetSkill: string;
|
|
23
|
-
readonly targetRoot: string;
|
|
24
|
-
readonly action: ProposalAction;
|
|
25
|
-
readonly patch?: SkillProposalPatch;
|
|
26
|
-
readonly createSpec?: SkillProposalCreateSpec;
|
|
27
|
-
readonly rationale: string;
|
|
28
|
-
readonly evidenceRefs: readonly string[];
|
|
29
|
-
readonly status: ProposalStatus;
|
|
30
|
-
readonly ledgerEntryId?: string;
|
|
31
|
-
}
|
|
32
|
-
export interface SkillBlobRef {
|
|
33
|
-
readonly algorithm: "sha256";
|
|
34
|
-
readonly digest: string;
|
|
35
|
-
readonly size: number;
|
|
36
|
-
}
|
|
37
|
-
export interface SkillSnapshotItem {
|
|
38
|
-
readonly file: string;
|
|
39
|
-
readonly blob: SkillBlobRef;
|
|
40
|
-
}
|
|
41
|
-
export interface SkillLedgerEntry {
|
|
42
|
-
readonly schema: "litfamily.skill-ledger/v1";
|
|
43
|
-
readonly id: string;
|
|
44
|
-
readonly createdAt: string;
|
|
45
|
-
readonly proposalId: string | null;
|
|
46
|
-
readonly actor: "user" | "curator";
|
|
47
|
-
readonly operation: "apply" | "reject" | "rollback" | "curator-transition";
|
|
48
|
-
readonly targetSkill: string;
|
|
49
|
-
readonly targetRoot: string;
|
|
50
|
-
readonly before: readonly SkillSnapshotItem[];
|
|
51
|
-
readonly after: readonly SkillSnapshotItem[];
|
|
52
|
-
readonly restoresLedgerEntryId?: string;
|
|
53
|
-
readonly reason?: string;
|
|
54
|
-
}
|
|
55
|
-
export interface SkillUsageEntry {
|
|
56
|
-
readonly state: "active" | "stale" | "archived";
|
|
57
|
-
readonly use_count: number;
|
|
58
|
-
readonly view_count: number;
|
|
59
|
-
readonly patch_count: number;
|
|
60
|
-
readonly created_at: string;
|
|
61
|
-
readonly created_by: "agent" | "user" | "installed" | "curator" | null;
|
|
62
|
-
readonly last_used_at: string | null;
|
|
63
|
-
readonly last_viewed_at: string | null;
|
|
64
|
-
readonly last_patched_at: string | null;
|
|
65
|
-
readonly archived_at: string | null;
|
|
66
|
-
readonly pinned: boolean;
|
|
67
|
-
}
|
|
68
|
-
export interface SkillUsageSidecar {
|
|
69
|
-
readonly schema: "litfamily.skill-usage/v1";
|
|
70
|
-
readonly updatedAt: string;
|
|
71
|
-
readonly skills: Readonly<Record<string, SkillUsageEntry>>;
|
|
72
|
-
}
|
|
73
|
-
export declare class SkillLoopError extends Error {
|
|
74
|
-
readonly code: string;
|
|
75
|
-
constructor(code: string, message: string);
|
|
76
|
-
}
|
|
77
|
-
export declare const SHIPPED_SKILL_IDS: Readonly<Set<string>>;
|
|
78
|
-
export declare function canonicalPathIdentity(path: string): string;
|
|
79
|
-
export declare function validateTargetSkillIdentity(skillId: string): string;
|
|
80
|
-
export declare function validateRelativeFile(file: string, targetSkill: string): string;
|
|
81
|
-
export declare function validateProposal(value: unknown, expectedRoot: string): SkillProposal;
|