skill-family-engineering-kit 0.5.0 → 0.6.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/CHANGELOG.md +22 -0
- package/CHANGELOG.zh-CN.md +22 -0
- package/README.md +12 -10
- package/README.zh-CN.md +12 -10
- package/package.json +3 -3
- package/release-notes/0.6.0.yaml +25 -0
- package/src/adopt-plan.mjs +81 -159
- package/src/check.mjs +443 -19
- package/src/cli.mjs +80 -1
- package/src/core-check.mjs +8 -4
- package/src/entry-check.mjs +384 -0
- package/src/errors.mjs +21 -0
- package/src/index.mjs +35 -6
- package/src/projection.mjs +221 -12
- package/src/relock.mjs +397 -0
- package/src/skeleton.mjs +162 -4
- package/CODE_OF_CONDUCT.md +0 -131
- package/CONTRIBUTING.md +0 -25
- package/SECURITY.md +0 -33
- package/docs/.nojekyll +0 -0
- package/docs/404.html +0 -1666
- package/docs/agents/architecture-routing/index.html +0 -1944
- package/docs/agents/capability-catalog.en.json +0 -1821
- package/docs/agents/capability-catalog.json +0 -1073
- package/docs/agents/capability-catalog.schema.json +0 -179
- package/docs/agents/capability-catalog.zh-CN.json +0 -1821
- package/docs/agents/index.html +0 -1914
- package/docs/architecture/index.html +0 -2194
- package/docs/assets/images/favicon.png +0 -0
- package/docs/assets/javascripts/bundle.d7400e89.min.js +0 -16
- package/docs/assets/javascripts/lunr/min/lunr.ar.min.js +0 -1
- package/docs/assets/javascripts/lunr/min/lunr.da.min.js +0 -18
- package/docs/assets/javascripts/lunr/min/lunr.de.min.js +0 -18
- package/docs/assets/javascripts/lunr/min/lunr.du.min.js +0 -18
- package/docs/assets/javascripts/lunr/min/lunr.el.min.js +0 -1
- package/docs/assets/javascripts/lunr/min/lunr.es.min.js +0 -18
- package/docs/assets/javascripts/lunr/min/lunr.fi.min.js +0 -18
- package/docs/assets/javascripts/lunr/min/lunr.fr.min.js +0 -18
- package/docs/assets/javascripts/lunr/min/lunr.he.min.js +0 -1
- package/docs/assets/javascripts/lunr/min/lunr.hi.min.js +0 -1
- package/docs/assets/javascripts/lunr/min/lunr.hu.min.js +0 -18
- package/docs/assets/javascripts/lunr/min/lunr.hy.min.js +0 -1
- package/docs/assets/javascripts/lunr/min/lunr.it.min.js +0 -18
- package/docs/assets/javascripts/lunr/min/lunr.ja.min.js +0 -1
- package/docs/assets/javascripts/lunr/min/lunr.jp.min.js +0 -1
- package/docs/assets/javascripts/lunr/min/lunr.kn.min.js +0 -1
- package/docs/assets/javascripts/lunr/min/lunr.ko.min.js +0 -1
- package/docs/assets/javascripts/lunr/min/lunr.multi.min.js +0 -1
- package/docs/assets/javascripts/lunr/min/lunr.nl.min.js +0 -18
- package/docs/assets/javascripts/lunr/min/lunr.no.min.js +0 -18
- package/docs/assets/javascripts/lunr/min/lunr.pt.min.js +0 -18
- package/docs/assets/javascripts/lunr/min/lunr.ro.min.js +0 -18
- package/docs/assets/javascripts/lunr/min/lunr.ru.min.js +0 -18
- package/docs/assets/javascripts/lunr/min/lunr.sa.min.js +0 -1
- package/docs/assets/javascripts/lunr/min/lunr.stemmer.support.min.js +0 -1
- package/docs/assets/javascripts/lunr/min/lunr.sv.min.js +0 -18
- package/docs/assets/javascripts/lunr/min/lunr.ta.min.js +0 -1
- package/docs/assets/javascripts/lunr/min/lunr.te.min.js +0 -1
- package/docs/assets/javascripts/lunr/min/lunr.th.min.js +0 -1
- package/docs/assets/javascripts/lunr/min/lunr.tr.min.js +0 -18
- package/docs/assets/javascripts/lunr/min/lunr.vi.min.js +0 -1
- package/docs/assets/javascripts/lunr/min/lunr.zh.min.js +0 -1
- package/docs/assets/javascripts/lunr/tinyseg.js +0 -206
- package/docs/assets/javascripts/lunr/wordcut.js +0 -6708
- package/docs/assets/javascripts/workers/search.2c215733.min.js +0 -42
- package/docs/assets/stylesheets/main.ec1eaa64.min.css +0 -1
- package/docs/assets/stylesheets/palette.ab4e12ef.min.css +0 -1
- package/docs/en/agents/architecture-routing/index.html +0 -1944
- package/docs/en/agents/index.html +0 -1925
- package/docs/en/architecture/index.html +0 -2196
- package/docs/en/examples-and-fixtures/index.html +0 -1888
- package/docs/en/help/index.html +0 -2035
- package/docs/en/index.html +0 -1895
- package/docs/en/licensing/index.html +0 -1917
- package/docs/en/migration/index.html +0 -2335
- package/docs/en/quickstart/index.html +0 -1919
- package/docs/en/recipes/adapter-text-closure/index.html +0 -2000
- package/docs/en/recipes/adopt-existing-repository/index.html +0 -1987
- package/docs/en/recipes/deterministic-human-report/index.html +0 -1998
- package/docs/en/recipes/domain-schema-validation/index.html +0 -2009
- package/docs/en/recipes/durable-local-state/index.html +0 -2009
- package/docs/en/recipes/host-profile-integration/index.html +0 -2001
- package/docs/en/recipes/index.html +0 -1877
- package/docs/en/recipes/safe-filesystem-and-atomic-write/index.html +0 -1994
- package/docs/en/reference/api/index.html +0 -1941
- package/docs/en/reference/compatibility/index.html +0 -2036
- package/docs/en/reference/failure-and-side-effect-matrix/index.html +0 -2216
- package/docs/examples-and-fixtures/index.html +0 -1871
- package/docs/git-lifecycle/index.html +0 -2029
- package/docs/help/index.html +0 -2035
- package/docs/index.html +0 -1893
- package/docs/integration/audit/baseline/audit-codes.json +0 -62
- package/docs/integration/audit/failure-evidence/index.html +0 -1925
- package/docs/integration/audit/independence/index.html +0 -1894
- package/docs/integration/audit/index.html +0 -1880
- package/docs/integration/audit/mutation-taxonomy/index.html +0 -2075
- package/docs/integration/audit/schemas/audit-evidence.schema.json +0 -182
- package/docs/integration/audit/version-compatibility/index.html +0 -1902
- package/docs/licensing/index.html +0 -1917
- package/docs/migration/index.html +0 -2335
- package/docs/public/status/index.html +0 -1884
- package/docs/quickstart/index.html +0 -1919
- package/docs/recipes/adapter-text-closure/index.html +0 -2000
- package/docs/recipes/adopt-existing-repository/index.html +0 -1987
- package/docs/recipes/deterministic-human-report/index.html +0 -1998
- package/docs/recipes/domain-schema-validation/index.html +0 -2009
- package/docs/recipes/durable-local-state/index.html +0 -2009
- package/docs/recipes/host-profile-integration/index.html +0 -2001
- package/docs/recipes/index.html +0 -1877
- package/docs/recipes/safe-filesystem-and-atomic-write/index.html +0 -1994
- package/docs/reference/api/contracts/index.html +0 -2558
- package/docs/reference/api/engineering-kit/index.html +0 -2577
- package/docs/reference/api/harness/index.html +0 -3029
- package/docs/reference/api/index.html +0 -1881
- package/docs/reference/compatibility/index.html +0 -2036
- package/docs/reference/failure-and-side-effect-matrix/index.html +0 -2216
- package/docs/search/search_index.json +0 -1
- package/docs/setup/index.html +0 -1995
- package/docs/sitemap.xml +0 -207
package/src/cli.mjs
CHANGED
|
@@ -4,6 +4,8 @@ import process from "node:process";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { readFileContained, resolveContained } from "skill-family-harness-node";
|
|
6
6
|
import { bundledProfilesRoot } from "./licensing.mjs";
|
|
7
|
+
import { checkEntriesAction } from "./entry-check.mjs";
|
|
8
|
+
import { relockAction } from "./relock.mjs";
|
|
7
9
|
import { checkReportAction, renderReportAction } from "./report.mjs";
|
|
8
10
|
import {
|
|
9
11
|
COMMAND_SIDE_EFFECTS,
|
|
@@ -56,6 +58,13 @@ const HELP_TEXT = `skill-family-engineering-kit —— 构建期工程工具包
|
|
|
56
58
|
报告子动作: check report 对一份已渲染报告做分级诊断:
|
|
57
59
|
硬失败(SFC3001/3002/3003)计为发现(退出码 1);
|
|
58
60
|
风格告警只报告、绝不阻塞机器正确的报告。
|
|
61
|
+
入口契约子动作: check entries 运行共享入口契约门禁
|
|
62
|
+
(SFA-ENTRY-003/004/005/007 + SFA-CONTEXT-001/002),
|
|
63
|
+
读取 skill-family.entry-contract.json 声明;声明缺失是数据、不是发现。
|
|
64
|
+
受控重锁子动作: check relock 运行受控 relock 事务(SG-36):
|
|
65
|
+
把新手写文件登记进 .foundation/file-registry.json,并以当前字节
|
|
66
|
+
重算 skill-family.managed-file-lock.json(单次 fail-closed 事务;
|
|
67
|
+
只写这两个收容状态文档,任何拒绝都零写入)。
|
|
59
68
|
|
|
60
69
|
全局选项:
|
|
61
70
|
--root <dir> 目标工作区根目录(默认当前目录)
|
|
@@ -119,7 +128,7 @@ function commandHelp(command) {
|
|
|
119
128
|
check: [
|
|
120
129
|
"选项:",
|
|
121
130
|
" --root <dir> 目标工作区(默认当前目录)",
|
|
122
|
-
" --only <class> 只运行一个诊断类: contracts|drift|closure|version|docs|git|identity",
|
|
131
|
+
" --only <class> 只运行一个诊断类: contracts|drift|closure|version|docs|git|identity|boundary|platform",
|
|
123
132
|
" --profiles-root <dir> 许可证 Profile 根目录",
|
|
124
133
|
" --no-git-spawn 禁用只读 git status 探测,仅用文件系统事实",
|
|
125
134
|
"",
|
|
@@ -232,6 +241,17 @@ const COMMAND_SPECS = {
|
|
|
232
241
|
"--binding": { key: "binding", value: true },
|
|
233
242
|
},
|
|
234
243
|
},
|
|
244
|
+
"check entries": {
|
|
245
|
+
flags: {
|
|
246
|
+
"--root": { key: "root", value: true },
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
"check relock": {
|
|
250
|
+
flags: {
|
|
251
|
+
"--root": { key: "root", value: true },
|
|
252
|
+
"--files": { key: "files", value: true },
|
|
253
|
+
},
|
|
254
|
+
},
|
|
235
255
|
"scaffold host-build": {
|
|
236
256
|
flags: {
|
|
237
257
|
"--root": { key: "root", value: true },
|
|
@@ -342,6 +362,53 @@ async function runReportSubAction(command, rest) {
|
|
|
342
362
|
}
|
|
343
363
|
}
|
|
344
364
|
|
|
365
|
+
async function runEntriesSubAction(rest) {
|
|
366
|
+
const specName = "check entries";
|
|
367
|
+
try {
|
|
368
|
+
const options = parseOptions(rest, { command: specName, flags: COMMAND_SPECS[specName].flags });
|
|
369
|
+
if (options.help) {
|
|
370
|
+
process.stdout.write(`${commandHelp("check")}\n`);
|
|
371
|
+
return KIT_EXIT_CODES.ok;
|
|
372
|
+
}
|
|
373
|
+
const { status, output } = await checkEntriesAction({ root: options.root });
|
|
374
|
+
process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
|
|
375
|
+
if (status === "ok") return KIT_EXIT_CODES.ok;
|
|
376
|
+
if (status === "findings") return KIT_EXIT_CODES.findings;
|
|
377
|
+
return KIT_EXIT_CODES.rejected;
|
|
378
|
+
} catch (error) {
|
|
379
|
+
printError(error);
|
|
380
|
+
return KIT_EXIT_CODES.rejected;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
async function runRelockSubAction(rest) {
|
|
385
|
+
const specName = "check relock";
|
|
386
|
+
try {
|
|
387
|
+
const options = parseOptions(rest, { command: specName, flags: COMMAND_SPECS[specName].flags });
|
|
388
|
+
if (options.help) {
|
|
389
|
+
process.stdout.write(`${commandHelp("check")}\n`);
|
|
390
|
+
return KIT_EXIT_CODES.ok;
|
|
391
|
+
}
|
|
392
|
+
// --files is one comma-separated relative-path list; absent means the
|
|
393
|
+
// auto-discovery mode of the closed-world stage (mirrors runRelock).
|
|
394
|
+
const files =
|
|
395
|
+
options.files === undefined
|
|
396
|
+
? null
|
|
397
|
+
: options.files
|
|
398
|
+
.split(",")
|
|
399
|
+
.map((rel) => rel.trim())
|
|
400
|
+
.filter((rel) => rel.length > 0);
|
|
401
|
+
const { status, output } = await relockAction({ root: options.root, files });
|
|
402
|
+
process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
|
|
403
|
+
// Relock has no findings state: it either commits the whole transaction
|
|
404
|
+
// or throws (exit 2). Any status other than "ok" is a mechanism error.
|
|
405
|
+
return status === "ok" ? KIT_EXIT_CODES.ok : KIT_EXIT_CODES.rejected;
|
|
406
|
+
} catch (error) {
|
|
407
|
+
printError(error);
|
|
408
|
+
return KIT_EXIT_CODES.rejected;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
345
412
|
export async function cliMain(argv) {
|
|
346
413
|
if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") {
|
|
347
414
|
process.stdout.write(HELP_TEXT);
|
|
@@ -359,6 +426,18 @@ export async function cliMain(argv) {
|
|
|
359
426
|
if ((command === "projection" || command === "check") && argv[1] === "report") {
|
|
360
427
|
return runReportSubAction(command, argv.slice(2));
|
|
361
428
|
}
|
|
429
|
+
// Positional entry-contract sub-action (SG-34): `check entries` runs the
|
|
430
|
+
// shared entry contract gate (SFA-ENTRY-003/004/005/007 + SFA-CONTEXT-001/002).
|
|
431
|
+
// It hangs under the existing check command; no fifth command is created.
|
|
432
|
+
if (command === "check" && argv[1] === "entries") {
|
|
433
|
+
return runEntriesSubAction(argv.slice(2));
|
|
434
|
+
}
|
|
435
|
+
// Positional controlled-relock sub-action (SG-36): `check relock` runs the
|
|
436
|
+
// fail-closed registration + lock-recompute transaction. It hangs under the
|
|
437
|
+
// existing check command, parallel to `check entries`; no fifth command.
|
|
438
|
+
if (command === "check" && argv[1] === "relock") {
|
|
439
|
+
return runRelockSubAction(argv.slice(2));
|
|
440
|
+
}
|
|
362
441
|
if (command === "scaffold" && argv[1] === "host-build") {
|
|
363
442
|
return runHostSubAction(command, "host-build", argv.slice(2));
|
|
364
443
|
}
|
package/src/core-check.mjs
CHANGED
|
@@ -5,7 +5,9 @@ import { CHECK_CLASSES, runChecks } from "./check.mjs";
|
|
|
5
5
|
import {
|
|
6
6
|
IDENTITY_RECORD_PATH,
|
|
7
7
|
MANAGED_LOCK_PATH,
|
|
8
|
+
PLATFORM_SUBSET_DECLARATION_PATH,
|
|
8
9
|
PROJECT_MANIFEST_PATH,
|
|
10
|
+
PUBLIC_BOUNDARY_DECLARATION_PATH,
|
|
9
11
|
} from "./skeleton.mjs";
|
|
10
12
|
import {
|
|
11
13
|
listTargetEntries,
|
|
@@ -23,7 +25,7 @@ import {
|
|
|
23
25
|
* ONLY import this function: safe file-tree enumeration, lstat/symlink/FIFO
|
|
24
26
|
* and special-entry rejection, file-registry classification (managed /
|
|
25
27
|
* handwritten / artifact closed world), declaration path containment, the
|
|
26
|
-
*
|
|
28
|
+
* nine check classes, and the stable finding/exit mapping all live here and
|
|
27
29
|
* nowhere else. No generated string may re-derive a recursive walk, an lstat
|
|
28
30
|
* classification, a glob match, or a local closed-world algorithm.
|
|
29
31
|
*
|
|
@@ -35,7 +37,7 @@ import {
|
|
|
35
37
|
* 3. tree enumeration — every symlink and special entry fails closed;
|
|
36
38
|
* every regular file must belong to the registered closed world
|
|
37
39
|
* (managed, handwritten, artifact, or a tracked tool lock);
|
|
38
|
-
* 4. the
|
|
40
|
+
* 4. the nine check classes through runChecks — skipped whenever an
|
|
39
41
|
* unsafe path (symlink/special at a managed or class-read path) would
|
|
40
42
|
* be opened by them: FIFOs must never be read (they block), symlinks
|
|
41
43
|
* must never be followed.
|
|
@@ -68,7 +70,7 @@ export const CORE_CHECK_SECURITY_KINDS = Object.freeze([
|
|
|
68
70
|
const CORE_CLASS = "core";
|
|
69
71
|
|
|
70
72
|
/**
|
|
71
|
-
* Paths the
|
|
73
|
+
* Paths the nine check classes open by name. They take part in the unsafe
|
|
72
74
|
* read-set even when they are not declared managed (README.md is handwritten
|
|
73
75
|
* in the skeleton), so a symlink/FIFO there can never hang or escape a read.
|
|
74
76
|
*/
|
|
@@ -79,6 +81,8 @@ const CLASS_READ_PATHS = Object.freeze([
|
|
|
79
81
|
MANAGED_LOCK_PATH,
|
|
80
82
|
IDENTITY_RECORD_PATH,
|
|
81
83
|
"identity-record.json",
|
|
84
|
+
PUBLIC_BOUNDARY_DECLARATION_PATH,
|
|
85
|
+
PLATFORM_SUBSET_DECLARATION_PATH,
|
|
82
86
|
]);
|
|
83
87
|
|
|
84
88
|
/** Lexical containment of a managed declaration; never touches the fs. */
|
|
@@ -189,7 +193,7 @@ export async function runCoreCheck({ root, allowGitSpawn = true, profilesRoot }
|
|
|
189
193
|
);
|
|
190
194
|
}
|
|
191
195
|
|
|
192
|
-
// ---- Stage 4: the
|
|
196
|
+
// ---- Stage 4: the nine check classes through the shared entry ---------
|
|
193
197
|
let classReport = null;
|
|
194
198
|
if (unsafeReadPaths.size === 0) {
|
|
195
199
|
classReport = await runChecks({ root: rootAbs, allowGitSpawn, profilesRoot });
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readFileContained,
|
|
3
|
+
estimateTokens,
|
|
4
|
+
TOKEN_ESTIMATOR_ID,
|
|
5
|
+
TOKEN_ESTIMATOR_VERSION,
|
|
6
|
+
TOKEN_ESTIMATION_ALGORITHM,
|
|
7
|
+
} from "skill-family-harness-node";
|
|
8
|
+
import {
|
|
9
|
+
consumeTokenEstimateStrict,
|
|
10
|
+
TOKEN_ESTIMATE_CONSUMPTION,
|
|
11
|
+
} from "skill-family-contracts";
|
|
12
|
+
import { KIT_ERROR_KINDS } from "./errors.mjs";
|
|
13
|
+
import { KIT_TOOL_NAME, KIT_VERSION } from "./skeleton.mjs";
|
|
14
|
+
import { readOptionalJson, resolveTargetRoot } from "./workspace.mjs";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* runEntryContractCheck — the shared entry contract gate (SG-34; audit
|
|
18
|
+
* friction F2, satisfaction carrier of PGM-001(c)).
|
|
19
|
+
*
|
|
20
|
+
* Before this sub-action existed, every project hand-wrote roughly 150 lines
|
|
21
|
+
* of equivalent entry-gate logic and owned its correctness alone. The five
|
|
22
|
+
* frozen checks implement exactly the semantics the audit rules define:
|
|
23
|
+
*
|
|
24
|
+
* 1. SFA-ENTRY-003 — every declared entry exists physically in its
|
|
25
|
+
* declared form (v1 freezes the single form skill_md: the physicalRef
|
|
26
|
+
* must be a readable regular file named SKILL.md);
|
|
27
|
+
* 2. SFA-ENTRY-004 — the outward logical name of every entry is the
|
|
28
|
+
* qualified name `<project>:<name>` and logical names are unique;
|
|
29
|
+
* 3. SFA-ENTRY-005 — entries declared as human entries never appear in
|
|
30
|
+
* the method-registration boundary (registeredMethods);
|
|
31
|
+
* 4. SFA-ENTRY-007 — entries flagged readOnlyRequired declare the
|
|
32
|
+
* read_only side-effect class (controlled vocabulary: read_only or
|
|
33
|
+
* transactional);
|
|
34
|
+
* 5. SFA-CONTEXT-001/002 — every entry's SKILL.md token budget is
|
|
35
|
+
* measured with the authoritative deterministic estimator and consumed
|
|
36
|
+
* through the frozen token estimate consumption contract (SG-33):
|
|
37
|
+
* tokens >= hardLimitTokens is a hard-line finding (SFA-CONTEXT-002),
|
|
38
|
+
* tokens >= warnLimitTokens a warn-line finding (SFA-CONTEXT-001).
|
|
39
|
+
* The estimate is never coerced: consumption failure is a mechanism
|
|
40
|
+
* error (fail closed), and degradation never relaxes the thresholds —
|
|
41
|
+
* inside the kit the authoritative estimator is a hard dependency, so
|
|
42
|
+
* no degraded path exists here.
|
|
43
|
+
*
|
|
44
|
+
* The input is one kit-owned declaration document
|
|
45
|
+
* (skill-family.entry-contract.json, kind
|
|
46
|
+
* skill-family.entry-contract-declaration). Absence of the declaration is
|
|
47
|
+
* data, never a finding — whether a project owes an entry contract depends
|
|
48
|
+
* on its entry surface. The gate is diagnosis only: it reads, estimates and
|
|
49
|
+
* reports; it never writes.
|
|
50
|
+
*
|
|
51
|
+
* Report shape: kind skill-family.entry-check-report; exit mapping
|
|
52
|
+
* 0 clean / 1 findings / 2 mechanism (thrown by the caller).
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
export const ENTRY_CONTRACT_DECLARATION_PATH = "skill-family.entry-contract.json";
|
|
56
|
+
export const ENTRY_CONTRACT_DECLARATION_KIND = "skill-family.entry-contract-declaration";
|
|
57
|
+
export const ENTRY_FORMS = Object.freeze(["skill_md"]);
|
|
58
|
+
export const ENTRY_SIDE_EFFECT_CLASSES = Object.freeze(["read_only", "transactional"]);
|
|
59
|
+
|
|
60
|
+
const SEMVER_PATTERN = /^[0-9]+\.[0-9]+\.[0-9]+$/;
|
|
61
|
+
const LOCAL_NAME_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
62
|
+
|
|
63
|
+
function finding(kind, code, message, extra) {
|
|
64
|
+
return { class: "entries", kind, code, message, ...(extra ?? {}) };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function isPlainObject(value) {
|
|
68
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Runs the five entry contract checks over one target.
|
|
73
|
+
* Options: { root }.
|
|
74
|
+
* Returns the entry-check report document. Never writes anywhere.
|
|
75
|
+
* Throws only for unusable inputs (an unreadable target root) and for
|
|
76
|
+
* fail-closed token consumption failures (mechanism errors, exit 2).
|
|
77
|
+
*/
|
|
78
|
+
export async function runEntryContractCheck({ root } = {}) {
|
|
79
|
+
const rootAbs = await resolveTargetRoot(root ?? ".");
|
|
80
|
+
const findings = [];
|
|
81
|
+
const data = {
|
|
82
|
+
declared: false,
|
|
83
|
+
documentState: "missing",
|
|
84
|
+
project: null,
|
|
85
|
+
declaredVersion: null,
|
|
86
|
+
entries: [],
|
|
87
|
+
estimator: {
|
|
88
|
+
source: "foundation-token-estimator",
|
|
89
|
+
id: TOKEN_ESTIMATOR_ID,
|
|
90
|
+
version: TOKEN_ESTIMATOR_VERSION,
|
|
91
|
+
algorithm: TOKEN_ESTIMATION_ALGORITHM,
|
|
92
|
+
consumptionContract: TOKEN_ESTIMATE_CONSUMPTION.kind,
|
|
93
|
+
numericField: TOKEN_ESTIMATE_CONSUMPTION.numericField,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const loaded = await readOptionalJson(rootAbs, ENTRY_CONTRACT_DECLARATION_PATH);
|
|
98
|
+
if (loaded.reason === "missing") {
|
|
99
|
+
return entryReport(data, findings);
|
|
100
|
+
}
|
|
101
|
+
data.declared = true;
|
|
102
|
+
if (!loaded.ok) {
|
|
103
|
+
data.documentState = "parse-failed";
|
|
104
|
+
findings.push(
|
|
105
|
+
finding(
|
|
106
|
+
KIT_ERROR_KINDS.CONTRACT_PARSE_FAILED,
|
|
107
|
+
"SFC2004",
|
|
108
|
+
`${ENTRY_CONTRACT_DECLARATION_PATH} is not valid JSON`,
|
|
109
|
+
{ path: ENTRY_CONTRACT_DECLARATION_PATH, documentState: data.documentState },
|
|
110
|
+
),
|
|
111
|
+
);
|
|
112
|
+
return entryReport(data, findings);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const declaration = loaded.value;
|
|
116
|
+
const invalid = (message, extra) =>
|
|
117
|
+
findings.push(
|
|
118
|
+
finding(KIT_ERROR_KINDS.ENTRY_CONTRACT_INVALID, "SFC2004", message, {
|
|
119
|
+
path: ENTRY_CONTRACT_DECLARATION_PATH,
|
|
120
|
+
...(extra ?? {}),
|
|
121
|
+
}),
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// ---- template shape -----------------------------------------------------
|
|
125
|
+
data.documentState = "ok";
|
|
126
|
+
if (!isPlainObject(declaration)) {
|
|
127
|
+
invalid("the declaration must be a JSON object");
|
|
128
|
+
return entryReport(data, findings);
|
|
129
|
+
}
|
|
130
|
+
if (declaration.schemaVersion !== 1) {
|
|
131
|
+
invalid(`schemaVersion must be exactly 1 (got ${JSON.stringify(declaration.schemaVersion ?? null)})`, {
|
|
132
|
+
field: "schemaVersion",
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
if (declaration.kind !== ENTRY_CONTRACT_DECLARATION_KIND) {
|
|
136
|
+
invalid(`kind must be "${ENTRY_CONTRACT_DECLARATION_KIND}" (got ${JSON.stringify(declaration.kind ?? null)})`, {
|
|
137
|
+
field: "kind",
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
if (typeof declaration.project !== "string" || !LOCAL_NAME_PATTERN.test(declaration.project)) {
|
|
141
|
+
invalid("project must be a kebab-case project id", { field: "project" });
|
|
142
|
+
} else {
|
|
143
|
+
data.project = declaration.project;
|
|
144
|
+
}
|
|
145
|
+
if (typeof declaration.declaredVersion !== "string" || !SEMVER_PATTERN.test(declaration.declaredVersion)) {
|
|
146
|
+
invalid("declaredVersion must be a semantic version string X.Y.Z", { field: "declaredVersion" });
|
|
147
|
+
} else {
|
|
148
|
+
data.declaredVersion = declaration.declaredVersion;
|
|
149
|
+
const packageDocument = await readOptionalJson(rootAbs, "package.json");
|
|
150
|
+
if (
|
|
151
|
+
packageDocument.ok &&
|
|
152
|
+
typeof packageDocument.value?.version === "string" &&
|
|
153
|
+
packageDocument.value.version !== declaration.declaredVersion
|
|
154
|
+
) {
|
|
155
|
+
findings.push(
|
|
156
|
+
finding(
|
|
157
|
+
KIT_ERROR_KINDS.ENTRY_CONTRACT_VERSION_DRIFT,
|
|
158
|
+
"SFC2004",
|
|
159
|
+
`entry contract declaration declares version ${declaration.declaredVersion}; the single version source (package.json) is ${packageDocument.value.version}`,
|
|
160
|
+
{
|
|
161
|
+
path: ENTRY_CONTRACT_DECLARATION_PATH,
|
|
162
|
+
declared: declaration.declaredVersion,
|
|
163
|
+
expected: packageDocument.value.version,
|
|
164
|
+
},
|
|
165
|
+
),
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const entries = Array.isArray(declaration.entries) ? declaration.entries : null;
|
|
171
|
+
if (entries === null) {
|
|
172
|
+
invalid("entries must be an array of entry declarations", { field: "entries" });
|
|
173
|
+
return entryReport(data, findings);
|
|
174
|
+
}
|
|
175
|
+
const registeredMethods = Array.isArray(declaration.registeredMethods)
|
|
176
|
+
? declaration.registeredMethods.filter((item) => typeof item === "string")
|
|
177
|
+
: [];
|
|
178
|
+
if (!Array.isArray(declaration.registeredMethods)) {
|
|
179
|
+
invalid("registeredMethods must be an array of registered method logical names", {
|
|
180
|
+
field: "registeredMethods",
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const tokenBudget = isPlainObject(declaration.tokenBudget) ? declaration.tokenBudget : null;
|
|
185
|
+
const warnLimitTokens = tokenBudget?.warnLimitTokens;
|
|
186
|
+
const hardLimitTokens = tokenBudget?.hardLimitTokens;
|
|
187
|
+
const budgetUsable =
|
|
188
|
+
Number.isInteger(warnLimitTokens) &&
|
|
189
|
+
warnLimitTokens > 0 &&
|
|
190
|
+
Number.isInteger(hardLimitTokens) &&
|
|
191
|
+
hardLimitTokens > 0;
|
|
192
|
+
if (!budgetUsable) {
|
|
193
|
+
invalid("tokenBudget.warnLimitTokens and tokenBudget.hardLimitTokens must be positive integers", {
|
|
194
|
+
field: "tokenBudget",
|
|
195
|
+
});
|
|
196
|
+
} else if (warnLimitTokens >= hardLimitTokens) {
|
|
197
|
+
invalid("tokenBudget.warnLimitTokens must be strictly below hardLimitTokens", { field: "tokenBudget" });
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ---- per-entry checks ----------------------------------------------------
|
|
201
|
+
const seenLogicalNames = new Set();
|
|
202
|
+
for (const [index, entry] of entries.entries()) {
|
|
203
|
+
const at = `entries[${index}]`;
|
|
204
|
+
if (!isPlainObject(entry)) {
|
|
205
|
+
invalid(`${at} must be an object`, { field: at });
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const name = typeof entry.name === "string" ? entry.name : null;
|
|
209
|
+
if (name === null || !LOCAL_NAME_PATTERN.test(name)) {
|
|
210
|
+
invalid(`${at}.name must be a kebab-case local entry name`, { field: `${at}.name` });
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const expectedLogicalName = data.project ? `${data.project}:${name}` : null;
|
|
214
|
+
|
|
215
|
+
// Check 2 (SFA-ENTRY-004): qualified logical name consistency + uniqueness.
|
|
216
|
+
if (typeof entry.logicalName !== "string" || entry.logicalName.length === 0) {
|
|
217
|
+
invalid(`${at}.logicalName must be a non-empty string`, { field: `${at}.logicalName` });
|
|
218
|
+
} else {
|
|
219
|
+
if (expectedLogicalName !== null && entry.logicalName !== expectedLogicalName) {
|
|
220
|
+
findings.push(
|
|
221
|
+
finding(
|
|
222
|
+
KIT_ERROR_KINDS.ENTRY_NAME_INCONSISTENT,
|
|
223
|
+
"SFC2004",
|
|
224
|
+
`entry '${name}' logical name is ${entry.logicalName}; the qualified name must be ${expectedLogicalName} (SFA-ENTRY-004)`,
|
|
225
|
+
{ entry: name, declared: entry.logicalName, expected: expectedLogicalName },
|
|
226
|
+
),
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
if (seenLogicalNames.has(entry.logicalName)) {
|
|
230
|
+
findings.push(
|
|
231
|
+
finding(
|
|
232
|
+
KIT_ERROR_KINDS.ENTRY_NAME_INCONSISTENT,
|
|
233
|
+
"SFC2004",
|
|
234
|
+
`logical name ${entry.logicalName} is declared more than once (SFA-ENTRY-004)`,
|
|
235
|
+
{ entry: name, declared: entry.logicalName },
|
|
236
|
+
),
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
seenLogicalNames.add(entry.logicalName);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const form = entry.form;
|
|
243
|
+
if (!ENTRY_FORMS.includes(form)) {
|
|
244
|
+
invalid(`${at}.form must be one of: ${ENTRY_FORMS.join(", ")} (got ${JSON.stringify(form ?? null)})`, {
|
|
245
|
+
field: `${at}.form`,
|
|
246
|
+
});
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
const physicalRef = typeof entry.physicalRef === "string" ? entry.physicalRef : null;
|
|
250
|
+
if (physicalRef === null || physicalRef.length === 0 || physicalRef.startsWith("/") || physicalRef.split("/").includes("..")) {
|
|
251
|
+
invalid(`${at}.physicalRef must be a contained relative path`, { field: `${at}.physicalRef` });
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (!physicalRef.endsWith("/SKILL.md") && physicalRef !== "SKILL.md") {
|
|
255
|
+
invalid(`${at}.physicalRef must name a SKILL.md file for form skill_md`, { field: `${at}.physicalRef` });
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Check 1 (SFA-ENTRY-003): physical presence in the declared form.
|
|
260
|
+
let text = null;
|
|
261
|
+
try {
|
|
262
|
+
text = await readFileContained(rootAbs, physicalRef, { encoding: "utf8" });
|
|
263
|
+
} catch (cause) {
|
|
264
|
+
const kind = cause && cause.details ? cause.details.kind : null;
|
|
265
|
+
if (kind === "missing-resource") {
|
|
266
|
+
findings.push(
|
|
267
|
+
finding(
|
|
268
|
+
KIT_ERROR_KINDS.ENTRY_PHYSICAL_MISSING,
|
|
269
|
+
"SFC2004",
|
|
270
|
+
`entry '${name}' physical SKILL.md is absent: ${physicalRef} (SFA-ENTRY-003)`,
|
|
271
|
+
{ entry: name, path: physicalRef },
|
|
272
|
+
),
|
|
273
|
+
);
|
|
274
|
+
} else {
|
|
275
|
+
// Escaping or unsafe references are target-supplied data: report, never follow.
|
|
276
|
+
findings.push(
|
|
277
|
+
finding(
|
|
278
|
+
KIT_ERROR_KINDS.ENTRY_PHYSICAL_MISSING,
|
|
279
|
+
"SFC2004",
|
|
280
|
+
`entry '${name}' physicalRef is not safely readable inside the target: ${physicalRef} (kind: ${kind ?? "unknown"})`,
|
|
281
|
+
{ entry: name, path: physicalRef, causeKind: kind ?? "unknown" },
|
|
282
|
+
),
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Check 3 (SFA-ENTRY-005): human entries stay outside method registration.
|
|
289
|
+
if (entry.humanEntry === true && entry.logicalName && registeredMethods.includes(entry.logicalName)) {
|
|
290
|
+
findings.push(
|
|
291
|
+
finding(
|
|
292
|
+
KIT_ERROR_KINDS.ENTRY_HUMAN_REGISTERED,
|
|
293
|
+
"SFC2004",
|
|
294
|
+
`human entry '${name}' appears in the method-registration boundary: ${entry.logicalName} (SFA-ENTRY-005)`,
|
|
295
|
+
{ entry: name, logicalName: entry.logicalName },
|
|
296
|
+
),
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Check 4 (SFA-ENTRY-007): read-only obligation vs declared side-effect class.
|
|
301
|
+
if (!ENTRY_SIDE_EFFECT_CLASSES.includes(entry.sideEffectClass)) {
|
|
302
|
+
invalid(
|
|
303
|
+
`${at}.sideEffectClass must be one of: ${ENTRY_SIDE_EFFECT_CLASSES.join(", ")} (got ${JSON.stringify(entry.sideEffectClass ?? null)})`,
|
|
304
|
+
{ field: `${at}.sideEffectClass` },
|
|
305
|
+
);
|
|
306
|
+
} else if (entry.readOnlyRequired === true && entry.sideEffectClass !== "read_only") {
|
|
307
|
+
findings.push(
|
|
308
|
+
finding(
|
|
309
|
+
KIT_ERROR_KINDS.ENTRY_SIDE_EFFECT_VIOLATION,
|
|
310
|
+
"SFC2004",
|
|
311
|
+
`entry '${name}' is obligated read-only but declares side-effect class ${entry.sideEffectClass} (SFA-ENTRY-007)`,
|
|
312
|
+
{ entry: name, declared: entry.sideEffectClass, expected: "read_only" },
|
|
313
|
+
),
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Check 5 (SFA-CONTEXT-001/002): token budget via the frozen consumption
|
|
318
|
+
// contract. consumeTokenEstimateStrict throws fail-closed on a
|
|
319
|
+
// non-consumable estimate; the throw propagates as a mechanism error.
|
|
320
|
+
let tokens = null;
|
|
321
|
+
if (budgetUsable && warnLimitTokens < hardLimitTokens) {
|
|
322
|
+
const record = estimateTokens(text);
|
|
323
|
+
tokens = consumeTokenEstimateStrict(record);
|
|
324
|
+
if (tokens >= hardLimitTokens) {
|
|
325
|
+
findings.push(
|
|
326
|
+
finding(
|
|
327
|
+
KIT_ERROR_KINDS.ENTRY_TOKEN_BUDGET_EXCEEDED,
|
|
328
|
+
"SFC2004",
|
|
329
|
+
`entry '${name}' SKILL.md reaches the hard token line (${tokens} >= ${hardLimitTokens}, SFA-CONTEXT-002)`,
|
|
330
|
+
{ entry: name, path: physicalRef, tokens, limit: hardLimitTokens, rule: "SFA-CONTEXT-002" },
|
|
331
|
+
),
|
|
332
|
+
);
|
|
333
|
+
} else if (tokens >= warnLimitTokens) {
|
|
334
|
+
findings.push(
|
|
335
|
+
finding(
|
|
336
|
+
KIT_ERROR_KINDS.ENTRY_TOKEN_BUDGET_EXCEEDED,
|
|
337
|
+
"SFC2004",
|
|
338
|
+
`entry '${name}' SKILL.md reaches the warn token line (${tokens} >= ${warnLimitTokens}, SFA-CONTEXT-001)`,
|
|
339
|
+
{ entry: name, path: physicalRef, tokens, limit: warnLimitTokens, rule: "SFA-CONTEXT-001" },
|
|
340
|
+
),
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
data.entries.push({
|
|
346
|
+
name,
|
|
347
|
+
logicalName: typeof entry.logicalName === "string" ? entry.logicalName : null,
|
|
348
|
+
physicalRef,
|
|
349
|
+
humanEntry: entry.humanEntry === true,
|
|
350
|
+
sideEffectClass: entry.sideEffectClass ?? null,
|
|
351
|
+
tokens,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return entryReport(data, findings);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function entryReport(data, findings) {
|
|
359
|
+
return {
|
|
360
|
+
kind: "skill-family.entry-check-report",
|
|
361
|
+
schemaVersion: 1,
|
|
362
|
+
generatedBy: { tool: KIT_TOOL_NAME, version: KIT_VERSION },
|
|
363
|
+
target: { root: "." },
|
|
364
|
+
declared: data.declared,
|
|
365
|
+
documentState: data.documentState,
|
|
366
|
+
ok: findings.length === 0,
|
|
367
|
+
exitCode: findings.length === 0 ? 0 : 1,
|
|
368
|
+
findings,
|
|
369
|
+
data,
|
|
370
|
+
policy:
|
|
371
|
+
"entry contract gate is diagnosis only: it reads declarations and SKILL.md bytes, estimates tokens through the authoritative estimator and the frozen consumption contract, and never writes",
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* CLI sub-action wrapper (positioned as `check entries`, parallel to
|
|
377
|
+
* `check report`). Returns { status, output } where status maps to the
|
|
378
|
+
* stable kit exit codes: ok -> 0, findings -> 1; mechanism failures throw
|
|
379
|
+
* and the CLI maps them to 2.
|
|
380
|
+
*/
|
|
381
|
+
export async function checkEntriesAction({ root } = {}) {
|
|
382
|
+
const output = await runEntryContractCheck({ root });
|
|
383
|
+
return { status: output.ok ? "ok" : "findings", output };
|
|
384
|
+
}
|
package/src/errors.mjs
CHANGED
|
@@ -52,6 +52,10 @@ export const KIT_ERROR_KINDS = Object.freeze({
|
|
|
52
52
|
CHECK_CLASS_FAILED: "check-class-failed",
|
|
53
53
|
MANAGED_FILE_MISSING: "managed-file-missing",
|
|
54
54
|
MANAGED_FILE_DRIFT: "managed-file-drift",
|
|
55
|
+
// never-built distinction (SG-37): every well-formed managed entry declared
|
|
56
|
+
// in the lock is absent on disk — the build outputs were never produced.
|
|
57
|
+
// Distinguished from content drift; carries build-first guidance.
|
|
58
|
+
NEVER_BUILT: "never-built",
|
|
55
59
|
SYMLINK_AT_MANAGED_PATH: "symlink-at-managed-path",
|
|
56
60
|
NOT_A_REGULAR_FILE: "not-a-regular-file",
|
|
57
61
|
CLOSURE_INPUT_MISSING: "closure-input-missing",
|
|
@@ -67,10 +71,27 @@ export const KIT_ERROR_KINDS = Object.freeze({
|
|
|
67
71
|
SYMLINK_ENTRY: "symlink-entry",
|
|
68
72
|
SPECIAL_ENTRY: "special-entry",
|
|
69
73
|
UNCONTAINED_DECLARATION: "uncontained-declaration",
|
|
74
|
+
// check extensions (audit remediation C2: SG-13/14, SG-17, SFA-PLAT-002)
|
|
75
|
+
VERSION_AUTHORITY_DUAL: "version-authority-dual",
|
|
76
|
+
VERSION_SINGLE_SOURCE_DRIFT: "version-single-source-drift",
|
|
77
|
+
PUBLIC_BOUNDARY_VIOLATION: "public-boundary-violation",
|
|
78
|
+
PLATFORM_SUBSET_INVALID: "platform-subset-declaration-invalid",
|
|
79
|
+
PLATFORM_SUBSET_VERSION_DRIFT: "platform-subset-version-drift",
|
|
80
|
+
// entry contract gate sub-action (SG-34: SFA-ENTRY-003/004/005/007 + SFA-CONTEXT-001/002)
|
|
81
|
+
ENTRY_CONTRACT_INVALID: "entry-contract-declaration-invalid",
|
|
82
|
+
ENTRY_CONTRACT_VERSION_DRIFT: "entry-contract-version-drift",
|
|
83
|
+
ENTRY_PHYSICAL_MISSING: "entry-physical-missing",
|
|
84
|
+
ENTRY_NAME_INCONSISTENT: "entry-name-inconsistent",
|
|
85
|
+
ENTRY_HUMAN_REGISTERED: "entry-human-registered",
|
|
86
|
+
ENTRY_SIDE_EFFECT_VIOLATION: "entry-side-effect-violation",
|
|
87
|
+
ENTRY_TOKEN_BUDGET_EXCEEDED: "entry-token-budget-exceeded",
|
|
70
88
|
// report sub-actions (projection report / check report, FND-ADR-005)
|
|
71
89
|
REPORT_INPUT_MISSING: "report-input-missing",
|
|
72
90
|
REPORT_PATH_CONFLICT: "report-path-conflict",
|
|
73
91
|
REPORT_WRITE_FAILED: "report-write-failed",
|
|
92
|
+
// controlled relock channel (SG-36; audit friction F5): `check relock`
|
|
93
|
+
RELOCK_REGISTRATION_REFUSED: "relock-registration-refused",
|
|
94
|
+
RELOCK_LOCK_DRIFT: "relock-lock-drift",
|
|
74
95
|
// read-only host integration slice
|
|
75
96
|
HOST_CONTRACT_INVALID: "host-contract-invalid",
|
|
76
97
|
HOST_PROBE_FAILED: "host-probe-failed",
|
package/src/index.mjs
CHANGED
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
import { ContractsError } from "skill-family-contracts";
|
|
2
|
-
import {
|
|
3
|
-
import { CHECK_CLASSES, DOCUMENT_STATES, runChecks } from "./check.mjs";
|
|
2
|
+
import { buildProfileDraft, planAdoption } from "./adopt-plan.mjs";
|
|
3
|
+
import { CHECK_CLASSES, DOCUMENT_STATES, PLATFORM_SUBSET_DECLARATION_KIND, runChecks } from "./check.mjs";
|
|
4
4
|
import { CORE_CHECK_SECURITY_KINDS, isContainedDeclaration, runCoreCheck } from "./core-check.mjs";
|
|
5
|
+
import {
|
|
6
|
+
checkEntriesAction,
|
|
7
|
+
ENTRY_CONTRACT_DECLARATION_KIND,
|
|
8
|
+
ENTRY_CONTRACT_DECLARATION_PATH,
|
|
9
|
+
ENTRY_FORMS,
|
|
10
|
+
ENTRY_SIDE_EFFECT_CLASSES,
|
|
11
|
+
runEntryContractCheck,
|
|
12
|
+
} from "./entry-check.mjs";
|
|
13
|
+
import {
|
|
14
|
+
RELOCK_LOCK_PATH_PATTERN,
|
|
15
|
+
RELOCK_REPORT_KIND,
|
|
16
|
+
relockAction,
|
|
17
|
+
runRelock,
|
|
18
|
+
} from "./relock.mjs";
|
|
5
19
|
import {
|
|
6
20
|
invalidParamsError,
|
|
7
21
|
KitError,
|
|
@@ -51,7 +65,7 @@ import {
|
|
|
51
65
|
listLicensingProfiles,
|
|
52
66
|
loadLicensingProfile,
|
|
53
67
|
} from "./licensing.mjs";
|
|
54
|
-
import { compileProjectionPlan, runProjection, loadProjectionManifest } from "./projection.mjs";
|
|
68
|
+
import { compileProjectionPlan, PROJECTION_AUTHORITY_BINDING_KINDS, runProjection, loadProjectionManifest } from "./projection.mjs";
|
|
55
69
|
import { checkReportAction, renderReportAction } from "./report.mjs";
|
|
56
70
|
import { scaffoldTarget } from "./scaffold.mjs";
|
|
57
71
|
import {
|
|
@@ -70,8 +84,10 @@ import {
|
|
|
70
84
|
KIT_VERSION,
|
|
71
85
|
MANAGED_LOCK_PATH,
|
|
72
86
|
normalizeSkeletonInputs,
|
|
87
|
+
PLATFORM_SUBSET_DECLARATION_PATH,
|
|
73
88
|
PROJECT_MANIFEST_PATH,
|
|
74
89
|
PROJECTION_MANIFEST_PATH,
|
|
90
|
+
PUBLIC_BOUNDARY_DECLARATION_PATH,
|
|
75
91
|
} from "./skeleton.mjs";
|
|
76
92
|
import { matchAnyGlob } from "./workspace.mjs";
|
|
77
93
|
|
|
@@ -126,7 +142,7 @@ export const COMMAND_SIDE_EFFECTS = Object.freeze({
|
|
|
126
142
|
check: Object.freeze({
|
|
127
143
|
summary: "契约/漂移/闭包/版本/文档事实/Git 前置状态诊断。",
|
|
128
144
|
sideEffect:
|
|
129
|
-
"none — diagnosis only: never writes, never auto-fixes; git is probed read-only (one frozen status query at most); the report sub-action (check report) grades one rendered report against its bound machine result and writes nothing",
|
|
145
|
+
"none — diagnosis only: never writes, never auto-fixes; git is probed read-only (one frozen status query at most); the report sub-action (check report) grades one rendered report against its bound machine result and writes nothing; the entries sub-action (check entries) runs the shared entry contract gate (SFA-ENTRY-003/004/005/007 + SFA-CONTEXT-001/002) over skill-family.entry-contract.json and SKILL.md bytes and writes nothing; the relock sub-action (check relock) is the one controlled exception: one fail-closed transaction writing exactly the two contained state documents (.foundation/file-registry.json and skill-family.managed-file-lock.json), with drift validation before the first write and zero writes on any refusal",
|
|
130
146
|
exitCodes: "0 无发现;1 有发现;2 拒绝/用法/机制错误",
|
|
131
147
|
}),
|
|
132
148
|
});
|
|
@@ -190,16 +206,27 @@ export {
|
|
|
190
206
|
export {
|
|
191
207
|
scaffoldTarget,
|
|
192
208
|
planAdoption,
|
|
193
|
-
|
|
194
|
-
HANDOFF_FIELDS,
|
|
209
|
+
buildProfileDraft,
|
|
195
210
|
compileProjectionPlan,
|
|
211
|
+
PROJECTION_AUTHORITY_BINDING_KINDS,
|
|
196
212
|
runProjection,
|
|
197
213
|
loadProjectionManifest,
|
|
198
214
|
renderReportAction,
|
|
199
215
|
checkReportAction,
|
|
216
|
+
runEntryContractCheck,
|
|
217
|
+
checkEntriesAction,
|
|
218
|
+
ENTRY_CONTRACT_DECLARATION_PATH,
|
|
219
|
+
ENTRY_CONTRACT_DECLARATION_KIND,
|
|
220
|
+
ENTRY_FORMS,
|
|
221
|
+
ENTRY_SIDE_EFFECT_CLASSES,
|
|
222
|
+
runRelock,
|
|
223
|
+
relockAction,
|
|
224
|
+
RELOCK_LOCK_PATH_PATTERN,
|
|
225
|
+
RELOCK_REPORT_KIND,
|
|
200
226
|
runChecks,
|
|
201
227
|
CHECK_CLASSES,
|
|
202
228
|
DOCUMENT_STATES,
|
|
229
|
+
PLATFORM_SUBSET_DECLARATION_KIND,
|
|
203
230
|
runCoreCheck,
|
|
204
231
|
CORE_CHECK_SECURITY_KINDS,
|
|
205
232
|
isContainedDeclaration,
|
|
@@ -217,6 +244,8 @@ export {
|
|
|
217
244
|
MANAGED_LOCK_PATH,
|
|
218
245
|
PROJECTION_MANIFEST_PATH,
|
|
219
246
|
IDENTITY_RECORD_PATH,
|
|
247
|
+
PUBLIC_BOUNDARY_DECLARATION_PATH,
|
|
248
|
+
PLATFORM_SUBSET_DECLARATION_PATH,
|
|
220
249
|
matchAnyGlob,
|
|
221
250
|
describeHost,
|
|
222
251
|
loadHostRegistry,
|