skill-family-engineering-kit 0.7.0 → 0.8.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 +17 -0
- package/CHANGELOG.zh-CN.md +17 -0
- package/README.md +11 -15
- package/README.zh-CN.md +11 -15
- package/docs/architecture/index.html +2 -2
- package/docs/integration/audit/index.html +2 -1
- package/docs/integration/audit/version-compatibility/index.html +4 -3
- package/docs/public/status/index.html +3 -3
- package/docs/search/search_index.json +1 -1
- package/package.json +3 -3
- package/profile-spi/extension-spi.json +25 -6
- package/profile-spi/index.mjs +85 -7
- package/profile-spi/profile-adoption.schema.json +3 -108
- package/profile-spi/project-profile.schema.json +25 -0
- package/release-notes/0.8.0.yaml +19 -0
- package/src/adopt-plan.mjs +22 -22
- package/src/skeleton.mjs +3 -3
package/package.json
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"url": "https://github.com/ifoohoo/skill-family-engineering-kit/issues"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"skill-family-contracts": "0.
|
|
12
|
-
"skill-family-harness-node": "0.
|
|
11
|
+
"skill-family-contracts": "0.8.0",
|
|
12
|
+
"skill-family-harness-node": "0.8.0"
|
|
13
13
|
},
|
|
14
14
|
"description": "Build-time scaffold, adoption planning, projection, and checks.",
|
|
15
15
|
"engines": {
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"url": "https://github.com/ifoohoo/skill-family-engineering-kit.git"
|
|
49
49
|
},
|
|
50
50
|
"type": "module",
|
|
51
|
-
"version": "0.
|
|
51
|
+
"version": "0.8.0",
|
|
52
52
|
"scripts": {
|
|
53
53
|
"check": "node --test",
|
|
54
54
|
"test": "node --test"
|
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"kind": "skill-family.profile-extension-spi",
|
|
4
|
-
"spiVersion":
|
|
4
|
+
"spiVersion": 3,
|
|
5
5
|
"status": "stable",
|
|
6
|
-
"description": "Stable extension SPI for Skill Family profiles. A profile may only DECLARE its finite differences from the common core;
|
|
7
|
-
"baseContracts": "1.
|
|
6
|
+
"description": "Stable extension SPI for Skill Family profiles and project adoption declarations. A profile may only DECLARE its finite differences from the common core; a project profile declares its adopting workspace. Enforcement is mechanical with stable result codes.",
|
|
7
|
+
"baseContracts": "1.7.0",
|
|
8
8
|
"descriptor": {
|
|
9
9
|
"schemaId": "https://contracts.skill-family.example/v1/profile-descriptor.json",
|
|
10
10
|
"object": "profile-descriptor",
|
|
11
11
|
"dialect": "2020-12",
|
|
12
12
|
"policy": "strict",
|
|
13
13
|
"file": "profile.json",
|
|
14
|
-
"note": "The descriptor is the
|
|
14
|
+
"note": "The descriptor is the provider intake surface. It is plain validated data (additionalProperties: false at every level), so a profile cannot smuggle behavior in through the descriptor itself. SPI v3 also defines the separate project-profile intake; provider validation composes the optional top-level sections `adoption` and `overrides` onto the contracts profile-descriptor schema before validating; the contracts package stays untouched.",
|
|
15
15
|
"extendedSchemaId": "https://spi.profiles.skill-family.example/v2/profile-descriptor-extended.json"
|
|
16
16
|
},
|
|
17
|
+
"projectProfile": {
|
|
18
|
+
"schemaId": "https://contracts.skill-family.example/v1/project-profile.json",
|
|
19
|
+
"object": "project-profile",
|
|
20
|
+
"dialect": "2020-12",
|
|
21
|
+
"policy": "strict",
|
|
22
|
+
"file": "profile.json",
|
|
23
|
+
"note": "The project profile is the scaffolded workspace adoption declaration and is never accepted by verifyProfile.",
|
|
24
|
+
"errorCode": "SPE1008"
|
|
25
|
+
},
|
|
17
26
|
"principles": [
|
|
18
27
|
"declaration-only: differences are data declarations interpreted by the core's own fixed logic; a profile never supplies behavior that the core executes as core logic",
|
|
19
28
|
"closed-core: no difference may add, override, or remove anything owned by the common core (contracts objects, registered protocols, kernel operations, core packages, kit commands)",
|
|
@@ -34,16 +43,21 @@
|
|
|
34
43
|
"adoption-pin-digests",
|
|
35
44
|
"overrides-policy"
|
|
36
45
|
],
|
|
46
|
+
"projectProfilePipeline": [
|
|
47
|
+
"project-profile-schema",
|
|
48
|
+
"adoption-pin-digests",
|
|
49
|
+
"overrides-policy"
|
|
50
|
+
],
|
|
37
51
|
"resultCodes": [
|
|
38
52
|
{
|
|
39
53
|
"code": "SPE0000",
|
|
40
54
|
"name": "OK",
|
|
41
|
-
"meaning": "The
|
|
55
|
+
"meaning": "The provider descriptor or project profile and all applicable declarations satisfy every SPI rule; the input is admitted as a declaration-only extension or project adoption declaration."
|
|
42
56
|
},
|
|
43
57
|
{
|
|
44
58
|
"code": "SPE1001",
|
|
45
59
|
"name": "DESCRIPTOR_INVALID",
|
|
46
|
-
"meaning": "The descriptor failed the
|
|
60
|
+
"meaning": "The descriptor failed the SPI-composed extended profile-descriptor schema under the declared dialect and strict policy, or could not be parsed as JSON. Adoption field completeness and pin format are schema-checked here. The observed contracts code (for example SFC1001) is carried in details.observedCode."
|
|
47
61
|
},
|
|
48
62
|
{
|
|
49
63
|
"code": "SPE1002",
|
|
@@ -74,6 +88,11 @@
|
|
|
74
88
|
"code": "SPE1007",
|
|
75
89
|
"name": "OVERRIDE_POLICY_VIOLATION",
|
|
76
90
|
"meaning": "An overrides entry violates the tightening-only policy: the rule id does not exist in the frozen rule baseline catalog, the parameter is not registered for that rule, the same rule/parameter pair is declared twice, or the numeric value does not strictly tighten the catalog baseline in the parameter's declared direction (relaxation and equality are both refused)."
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"code": "SPE1008",
|
|
94
|
+
"name": "PROJECT_PROFILE_INVALID",
|
|
95
|
+
"meaning": "The project-level profile cannot be read, parsed, or validated against the project-profile contract. Pin and override failures retain SPE1006 and SPE1007."
|
|
77
96
|
}
|
|
78
97
|
],
|
|
79
98
|
"extensionPoints": [
|
package/profile-spi/index.mjs
CHANGED
|
@@ -24,7 +24,7 @@ const SPI_DEFINITION = JSON.parse(
|
|
|
24
24
|
await readFile(new URL("./extension-spi.json", import.meta.url), "utf8"),
|
|
25
25
|
);
|
|
26
26
|
|
|
27
|
-
// SPI
|
|
27
|
+
// SPI v3 type definitions for the optional `adoption` and `overrides`
|
|
28
28
|
// descriptor sections (remediation handoff D-8/D-6). The contracts package
|
|
29
29
|
// stays untouched: the loader composes these $defs onto the contracts
|
|
30
30
|
// profile-descriptor schema at load time and validates descriptors against
|
|
@@ -47,11 +47,24 @@ const BASE_DESCRIPTOR_SCHEMA = JSON.parse(
|
|
|
47
47
|
),
|
|
48
48
|
);
|
|
49
49
|
|
|
50
|
+
const PROJECT_PROFILE_SCHEMA = JSON.parse(
|
|
51
|
+
await readFile(
|
|
52
|
+
new URL("./project-profile.schema.json", import.meta.url),
|
|
53
|
+
"utf8",
|
|
54
|
+
),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
export const PROJECT_PROFILE_SCHEMA_ID = PROJECT_PROFILE_SCHEMA.$id;
|
|
58
|
+
|
|
59
|
+
export function loadProjectProfileSchema() {
|
|
60
|
+
return structuredClone(PROJECT_PROFILE_SCHEMA);
|
|
61
|
+
}
|
|
62
|
+
|
|
50
63
|
/** $id of the SPI-composed descriptor schema (distinct from the contracts $id on purpose). */
|
|
51
64
|
export const EXTENDED_DESCRIPTOR_SCHEMA_ID = SPI_DEFINITION.descriptor.extendedSchemaId;
|
|
52
65
|
|
|
53
66
|
/**
|
|
54
|
-
* Composes the SPI
|
|
67
|
+
* Composes the SPI v3 extended descriptor schema: the frozen contracts
|
|
55
68
|
* profile-descriptor schema plus the optional top-level `adoption` and
|
|
56
69
|
* `overrides` sections defined by profiles/spi/profile-adoption.schema.json.
|
|
57
70
|
* Returns a fresh document on every call; callers never mutate it.
|
|
@@ -70,7 +83,7 @@ export function loadRuleBaselineCatalog() {
|
|
|
70
83
|
return RULE_BASELINE_CATALOG;
|
|
71
84
|
}
|
|
72
85
|
|
|
73
|
-
/** Frozen SPI result codes: SPE0000 plus SPE1001..
|
|
86
|
+
/** Frozen SPI result codes: SPE0000 plus SPE1001..SPE1008. */
|
|
74
87
|
export const SPI_RESULT_CODES = Object.freeze(
|
|
75
88
|
Object.fromEntries(SPI_DEFINITION.resultCodes.map((entry) => [entry.code, entry.name])),
|
|
76
89
|
);
|
|
@@ -283,7 +296,7 @@ export function assessOverridesPolicy(overrides) {
|
|
|
283
296
|
* failing step decides the stable result code:
|
|
284
297
|
*
|
|
285
298
|
* SPE1001 descriptor invalid (schema/parse; carries observedCode). Since
|
|
286
|
-
* SPI
|
|
299
|
+
* SPI v3 the descriptor is validated against the SPI-composed
|
|
287
300
|
* extended schema that admits the optional `adoption` and
|
|
288
301
|
* `overrides` sections; adoption field completeness and pin
|
|
289
302
|
* format are schema-checked here.
|
|
@@ -330,8 +343,8 @@ export async function verifyProfile({ profileRoot, descriptorRelPath } = {}) {
|
|
|
330
343
|
}
|
|
331
344
|
const validation = validateDocument(descriptor, {
|
|
332
345
|
schema: loadExtendedDescriptorSchema(),
|
|
333
|
-
dialect: SPI_DEFINITION.
|
|
334
|
-
policy: SPI_DEFINITION.
|
|
346
|
+
dialect: SPI_DEFINITION.projectProfile.dialect,
|
|
347
|
+
policy: SPI_DEFINITION.projectProfile.policy,
|
|
335
348
|
});
|
|
336
349
|
if (!validation.valid) {
|
|
337
350
|
return result("SPE1001", {
|
|
@@ -459,7 +472,7 @@ export async function verifyProfile({ profileRoot, descriptorRelPath } = {}) {
|
|
|
459
472
|
}
|
|
460
473
|
const keys = Object.keys(exported).sort();
|
|
461
474
|
const allowed = [...DECLARATION_SHAPE.allowedKeySet].sort();
|
|
462
|
-
if (keys.join("
|
|
475
|
+
if (keys.join("\0") !== allowed.join("\0")) {
|
|
463
476
|
return result("SPE1002", {
|
|
464
477
|
profileId,
|
|
465
478
|
reason: "declaration-shape-keys",
|
|
@@ -568,6 +581,71 @@ export async function verifyProfile({ profileRoot, descriptorRelPath } = {}) {
|
|
|
568
581
|
});
|
|
569
582
|
}
|
|
570
583
|
|
|
584
|
+
/**
|
|
585
|
+
* Verifies the project-level adoption declaration emitted by scaffold.
|
|
586
|
+
* This intake is deliberately separate from verifyProfile: a project profile
|
|
587
|
+
* declares the adopting workspace, while a descriptor declares a provider.
|
|
588
|
+
*/
|
|
589
|
+
export async function verifyProjectProfile({ projectRoot, profileRelPath } = {}) {
|
|
590
|
+
if (typeof projectRoot !== "string" || projectRoot.length === 0) {
|
|
591
|
+
throw new TypeError("verifyProjectProfile: projectRoot must be a non-empty path string");
|
|
592
|
+
}
|
|
593
|
+
const relPath = profileRelPath ?? "profile.json";
|
|
594
|
+
const projectSteps = [];
|
|
595
|
+
let raw;
|
|
596
|
+
try {
|
|
597
|
+
raw = await readFileContained(projectRoot, relPath, { encoding: "utf8" });
|
|
598
|
+
} catch (cause) {
|
|
599
|
+
return result("SPE1008", { reason: "project-profile-unreadable", details: { kind: cause?.details?.kind ?? "unknown", profile: relPath }, steps: projectSteps });
|
|
600
|
+
}
|
|
601
|
+
let document;
|
|
602
|
+
try {
|
|
603
|
+
document = JSON.parse(raw);
|
|
604
|
+
} catch {
|
|
605
|
+
return result("SPE1008", { reason: "project-profile-parse-failed", details: { profile: relPath }, steps: projectSteps });
|
|
606
|
+
}
|
|
607
|
+
const validation = validateDocument(document, {
|
|
608
|
+
schema: loadProjectProfileSchema(),
|
|
609
|
+
dialect: SPI_DEFINITION.descriptor.dialect,
|
|
610
|
+
policy: SPI_DEFINITION.descriptor.policy,
|
|
611
|
+
});
|
|
612
|
+
if (!validation.valid) {
|
|
613
|
+
return result("SPE1008", {
|
|
614
|
+
reason: "project-profile-schema-invalid",
|
|
615
|
+
details: { observedCode: validation.errorCode, errors: validation.errors.slice(0, 5) },
|
|
616
|
+
steps: projectSteps,
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
projectSteps.push("project-profile-schema");
|
|
620
|
+
const projectProfile = validation.data;
|
|
621
|
+
let digestReport;
|
|
622
|
+
try {
|
|
623
|
+
digestReport = await verifyAdoptionDigests({ profileRoot: projectRoot, adoption: projectProfile.adoption });
|
|
624
|
+
} catch (cause) {
|
|
625
|
+
return result("SPE1008", { reason: "project-profile-adoption-invalid", details: { message: cause?.message ?? String(cause) }, steps: projectSteps });
|
|
626
|
+
}
|
|
627
|
+
if (!digestReport.ok) {
|
|
628
|
+
const failed = digestReport.results.find((entry) => entry.status !== "verified");
|
|
629
|
+
return result("SPE1006", {
|
|
630
|
+
reason: failed.status === "artifact-unreadable" ? "pin-artifact-unreadable" : "pin-digest-mismatch",
|
|
631
|
+
details: { package: failed.package, path: failed.path, declaredSha256: failed.declaredSha256, actualSha256: failed.actualSha256, containmentKind: failed.containmentKind ?? null },
|
|
632
|
+
steps: projectSteps,
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
projectSteps.push("adoption-pin-digests");
|
|
636
|
+
const assessment = assessOverridesPolicy(projectProfile.overrides);
|
|
637
|
+
if (!assessment.ok) {
|
|
638
|
+
const [finding] = assessment.findings;
|
|
639
|
+
return result("SPE1007", { reason: finding.kind, details: { ...finding }, steps: projectSteps });
|
|
640
|
+
}
|
|
641
|
+
projectSteps.push("overrides-policy");
|
|
642
|
+
return result("SPE0000", {
|
|
643
|
+
projectId: projectProfile.project.id,
|
|
644
|
+
details: { adoptionDeclared: true, overrides: projectProfile.overrides.length },
|
|
645
|
+
steps: projectSteps,
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
|
|
571
649
|
/** The frozen SPI definition document. */
|
|
572
650
|
export function loadSpiDefinition() {
|
|
573
651
|
return SPI_DEFINITION;
|
|
@@ -2,119 +2,14 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://spi.profiles.skill-family.example/v2/profile-adoption.json",
|
|
4
4
|
"title": "ProfileAdoptionAndOverridesSections",
|
|
5
|
-
"description": "SPI
|
|
5
|
+
"description": "SPI v3 forwarding schema for provider descriptor adoption and overrides sections. Shape definitions remain owned by the Contracts profile-adoption-declaration schema.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"$defs": {
|
|
8
|
-
"pinnedPackage": {
|
|
9
|
-
"type": "object",
|
|
10
|
-
"additionalProperties": false,
|
|
11
|
-
"required": ["version", "path", "sha256"],
|
|
12
|
-
"properties": {
|
|
13
|
-
"version": {
|
|
14
|
-
"type": "string",
|
|
15
|
-
"pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$",
|
|
16
|
-
"description": "Exact pinned version of this Foundation package."
|
|
17
|
-
},
|
|
18
|
-
"path": {
|
|
19
|
-
"type": "string",
|
|
20
|
-
"pattern": "^[A-Za-z0-9._-]+(/[A-Za-z0-9._-]+)*$",
|
|
21
|
-
"description": "Relative POSIX path of the pinned artifact inside the profile write set; verifyProfile reads it through the containment layer and compares its real sha256 against the declared digest (GK-4 real-file discipline)."
|
|
22
|
-
},
|
|
23
|
-
"sha256": {
|
|
24
|
-
"type": "string",
|
|
25
|
-
"pattern": "^[0-9a-f]{64}$",
|
|
26
|
-
"description": "Declared sha256 hex digest of the pinned artifact bytes."
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
8
|
"adoption": {
|
|
31
|
-
"
|
|
32
|
-
"description": "Foundation adoption declaration (minimal D-8 field set). Presence is optional; when present every field is mandatory and mechanically verified by verifyProfile.",
|
|
33
|
-
"additionalProperties": false,
|
|
34
|
-
"required": ["foundation_profile", "foundation_pin", "adopted_at"],
|
|
35
|
-
"properties": {
|
|
36
|
-
"foundation_profile": {
|
|
37
|
-
"type": "object",
|
|
38
|
-
"additionalProperties": false,
|
|
39
|
-
"required": ["id", "version", "stability"],
|
|
40
|
-
"properties": {
|
|
41
|
-
"id": {
|
|
42
|
-
"type": "string",
|
|
43
|
-
"pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$",
|
|
44
|
-
"description": "Stable kebab-case identifier of the adopted Foundation profile."
|
|
45
|
-
},
|
|
46
|
-
"version": {
|
|
47
|
-
"type": "integer",
|
|
48
|
-
"minimum": 1,
|
|
49
|
-
"description": "Integer version of the adopted Foundation profile."
|
|
50
|
-
},
|
|
51
|
-
"stability": {
|
|
52
|
-
"enum": ["stable", "candidate-only"],
|
|
53
|
-
"description": "Stability class of the adopted Foundation profile at adoption time."
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
"foundation_pin": {
|
|
58
|
-
"type": "object",
|
|
59
|
-
"description": "Pin of all three Foundation packages (contracts, harness-node, engineering-kit) with exact versions and artifact sha256 digests.",
|
|
60
|
-
"additionalProperties": false,
|
|
61
|
-
"required": ["algorithm", "packages"],
|
|
62
|
-
"properties": {
|
|
63
|
-
"algorithm": {
|
|
64
|
-
"const": "sha256",
|
|
65
|
-
"description": "Digest algorithm; sha256 is the only admitted value."
|
|
66
|
-
},
|
|
67
|
-
"packages": {
|
|
68
|
-
"type": "object",
|
|
69
|
-
"additionalProperties": false,
|
|
70
|
-
"required": [
|
|
71
|
-
"skill-family-contracts",
|
|
72
|
-
"skill-family-harness-node",
|
|
73
|
-
"skill-family-engineering-kit"
|
|
74
|
-
],
|
|
75
|
-
"properties": {
|
|
76
|
-
"skill-family-contracts": { "$ref": "#/$defs/pinnedPackage" },
|
|
77
|
-
"skill-family-harness-node": { "$ref": "#/$defs/pinnedPackage" },
|
|
78
|
-
"skill-family-engineering-kit": { "$ref": "#/$defs/pinnedPackage" }
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
},
|
|
83
|
-
"adopted_at": {
|
|
84
|
-
"type": "string",
|
|
85
|
-
"format": "date-time",
|
|
86
|
-
"description": "RFC 3339 date-time at which the adoption took place."
|
|
87
|
-
}
|
|
88
|
-
}
|
|
9
|
+
"$ref": "https://contracts.skill-family.example/v1/profile-adoption-declaration.json#/$defs/adoption"
|
|
89
10
|
},
|
|
90
11
|
"overrides": {
|
|
91
|
-
"
|
|
92
|
-
"description": "Self-tightening override declarations (handoff D-6): each entry reuses an existing rule id with a project-level numeric value. Overrides may only tighten the baseline, never relax it; every entry is machine-checked by verifyProfile against the frozen rule baseline catalog (profiles/spi/rule-baseline-catalog.json).",
|
|
93
|
-
"items": {
|
|
94
|
-
"type": "object",
|
|
95
|
-
"additionalProperties": false,
|
|
96
|
-
"required": ["ruleId", "parameter", "value"],
|
|
97
|
-
"properties": {
|
|
98
|
-
"ruleId": {
|
|
99
|
-
"type": "string",
|
|
100
|
-
"pattern": "^SFA-[A-Z0-9]+-[0-9]{3}$",
|
|
101
|
-
"description": "Canonical rule id being tightened (no new rule id is minted; the original id is reused)."
|
|
102
|
-
},
|
|
103
|
-
"parameter": {
|
|
104
|
-
"type": "string",
|
|
105
|
-
"pattern": "^[a-zA-Z][a-zA-Z0-9]*$",
|
|
106
|
-
"description": "Named numeric parameter of the rule, as registered in the rule baseline catalog."
|
|
107
|
-
},
|
|
108
|
-
"value": {
|
|
109
|
-
"type": "number",
|
|
110
|
-
"description": "Project-level override value; must strictly tighten the catalog baseline in the parameter's declared direction."
|
|
111
|
-
},
|
|
112
|
-
"note": {
|
|
113
|
-
"type": "string",
|
|
114
|
-
"description": "Optional human rationale; not consumed mechanically."
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
12
|
+
"$ref": "https://contracts.skill-family.example/v1/profile-adoption-declaration.json#/$defs/overrides"
|
|
118
13
|
}
|
|
119
14
|
}
|
|
120
15
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://contracts.skill-family.example/v1/project-profile.json",
|
|
4
|
+
"title": "ProjectProfile",
|
|
5
|
+
"description": "Declares a project's Skill Family adoption and tightening overrides.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["schemaVersion", "kind", "project", "adoption", "overrides"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"schemaVersion": { "const": 1 },
|
|
11
|
+
"kind": { "const": "skill-family.project-profile" },
|
|
12
|
+
"project": {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"additionalProperties": false,
|
|
15
|
+
"required": ["id", "workspace"],
|
|
16
|
+
"properties": {
|
|
17
|
+
"id": { "type": "string", "pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$" },
|
|
18
|
+
"workspace": { "type": "string", "minLength": 1 }
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"adoption": { "$ref": "https://contracts.skill-family.example/v1/profile-adoption-declaration.json#/$defs/adoption" },
|
|
22
|
+
"overrides": { "$ref": "https://contracts.skill-family.example/v1/profile-adoption-declaration.json#/$defs/overrides" },
|
|
23
|
+
"note": { "type": "string" }
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
version: 0.8.0
|
|
2
|
+
date: 2026-08-21
|
|
3
|
+
locales:
|
|
4
|
+
en:
|
|
5
|
+
summary: Profile SPI v3 adds direct verification for scaffolded Project Profiles while descriptor verification remains stable.
|
|
6
|
+
changes:
|
|
7
|
+
added:
|
|
8
|
+
- Adds verifyProjectProfile({ projectRoot, profileRelPath? }) for skill-family.project-profile declarations.
|
|
9
|
+
- Keeps verifyProfile descriptor-only and reuses Contracts-owned adoption and overrides field definitions.
|
|
10
|
+
- Adds SPE1008 PROJECT_PROFILE_INVALID; SPE1006 and SPE1007 retain their existing meanings.
|
|
11
|
+
upgradeNotes: Project-root consumers must pin engineering-kit 0.8.0 and call verifyProjectProfile. Descriptor consumers continue to call verifyProfile; 0.7.0 remains available.
|
|
12
|
+
zh-CN:
|
|
13
|
+
summary: Profile SPI v3 新增 scaffold 项目 Profile 的直接校验,descriptor 校验保持兼容。
|
|
14
|
+
changes:
|
|
15
|
+
added:
|
|
16
|
+
- 新增 verifyProjectProfile({ projectRoot, profileRelPath? }),用于校验 skill-family.project-profile 声明。
|
|
17
|
+
- verifyProfile 继续只校验 descriptor,并复用 Contracts 拥有的 adoption 与 overrides 字段定义。
|
|
18
|
+
- 新增 SPE1008 PROJECT_PROFILE_INVALID;SPE1006 与 SPE1007 含义保持不变。
|
|
19
|
+
upgradeNotes: 项目根消费者必须锁定 engineering-kit 0.8.0 并调用 verifyProjectProfile;descriptor 消费者继续调用 verifyProfile,0.7.0 仍可使用。
|
package/src/adopt-plan.mjs
CHANGED
|
@@ -54,12 +54,12 @@ async function readExistingBytes(rootAbs, relPath) {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
|
-
* The per-adoption profile draft (SPI
|
|
57
|
+
* The per-adoption project profile draft (SPI v3; remediation handoff C3/D-8).
|
|
58
58
|
*
|
|
59
59
|
* Replaces the deprecated ten-field hand-off draft of the adoption-lock
|
|
60
60
|
* era: the adoption-lock concept and its artifact form are abolished, and
|
|
61
61
|
* the lightweight adoption proof is the profile.json descriptor's own
|
|
62
|
-
* adoption declaration, machine-verified by
|
|
62
|
+
* adoption declaration, machine-verified by verifyProjectProfile:
|
|
63
63
|
* descriptor-schema completeness of the minimal adoption field set, GK-4
|
|
64
64
|
* real-file digest discipline (SPE1006), and the tightening-only overrides
|
|
65
65
|
* policy (SPE1007).
|
|
@@ -100,7 +100,8 @@ export function buildProfileDraft({ inputs, migrationManifest, writeSet, entries
|
|
|
100
100
|
.map((pkg) => [pkg.name, pkg]),
|
|
101
101
|
);
|
|
102
102
|
const loadedVersions = Object.freeze({
|
|
103
|
-
"skill-family-contracts":
|
|
103
|
+
"skill-family-contracts": KIT_VERSION,
|
|
104
|
+
"skill-family-harness-node": KIT_VERSION,
|
|
104
105
|
"skill-family-engineering-kit": KIT_VERSION,
|
|
105
106
|
});
|
|
106
107
|
const packages = {};
|
|
@@ -114,7 +115,7 @@ export function buildProfileDraft({ inputs, migrationManifest, writeSet, entries
|
|
|
114
115
|
packages[name] = { version, path: null, sha256: null };
|
|
115
116
|
}
|
|
116
117
|
|
|
117
|
-
incompleteFields.push("
|
|
118
|
+
incompleteFields.push("adoption.adopted_at");
|
|
118
119
|
|
|
119
120
|
// Binding digests: the target file-set summary and the Foundation plan.
|
|
120
121
|
const targetSetDigest = digestBytes(
|
|
@@ -124,31 +125,30 @@ export function buildProfileDraft({ inputs, migrationManifest, writeSet, entries
|
|
|
124
125
|
Buffer.from(writeSet.map((item) => `${item.path}:${item.action}:${item.sha256}`).sort().join("\n"), "utf8"),
|
|
125
126
|
);
|
|
126
127
|
|
|
127
|
-
|
|
128
|
-
kind: "skill-family.profile-draft",
|
|
128
|
+
const projectProfile = {
|
|
129
129
|
schemaVersion: 1,
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
differences: [],
|
|
137
|
-
spi: [],
|
|
138
|
-
adoption: {
|
|
139
|
-
foundation_profile: foundationProfile,
|
|
140
|
-
foundation_pin: { algorithm: "sha256", packages },
|
|
141
|
-
adopted_at: null,
|
|
142
|
-
},
|
|
143
|
-
overrides: [],
|
|
130
|
+
kind: "skill-family.project-profile",
|
|
131
|
+
project: { id: inputs.projectId, workspace: `${inputs.projectId}-workspace` },
|
|
132
|
+
adoption: {
|
|
133
|
+
foundation_profile: foundationProfile,
|
|
134
|
+
foundation_pin: { algorithm: "sha256", packages },
|
|
135
|
+
adopted_at: null,
|
|
144
136
|
},
|
|
137
|
+
overrides: [],
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
kind: "skill-family.project-profile-draft",
|
|
142
|
+
schemaVersion: 1,
|
|
143
|
+
profileRelPath: "profile.json",
|
|
144
|
+
projectProfile,
|
|
145
145
|
overridesGuidance:
|
|
146
|
-
"overrides stays empty by default. An override is admissible only when it reuses an existing rule id with a project-level numeric value that strictly tightens the frozen rule baseline catalog in the parameter's declared direction (not-increase: strictly less; not-decrease: strictly greater). Equality and relaxation are refused (
|
|
146
|
+
"overrides stays empty by default. An override is admissible only when it reuses an existing rule id with a project-level numeric value that strictly tightens the frozen rule baseline catalog in the parameter's declared direction (not-increase: strictly less; not-decrease: strictly greater). Equality and relaxation are refused (SPE1007); risk identification for misuse of tightening belongs to skill-failure-auditor, not to this mechanical check.",
|
|
147
147
|
incompleteFields,
|
|
148
148
|
ready: incompleteFields.length === 0,
|
|
149
149
|
binding: { targetSetDigest, foundationPlanDigest },
|
|
150
150
|
policyNote:
|
|
151
|
-
"adoption-lock is deprecated (remediation handoff D-8): the kit no longer produces adoption-lock-form artifacts. The adoption proof is this
|
|
151
|
+
"adoption-lock is deprecated (remediation handoff D-8): the kit no longer produces adoption-lock-form artifacts. The adoption proof is this project's adoption declaration, completed with real artifact paths and digests inside the profile write set and machine-verified by verifyProjectProfile (SPE1006 digest discipline, SPE1007 tightening-only overrides).",
|
|
152
152
|
};
|
|
153
153
|
}
|
|
154
154
|
|
package/src/skeleton.mjs
CHANGED
|
@@ -398,9 +398,9 @@ function renderProjectProfile({ projectId, profileId }) {
|
|
|
398
398
|
foundation_pin: {
|
|
399
399
|
algorithm: "sha256",
|
|
400
400
|
packages: {
|
|
401
|
-
"skill-family-contracts": { version:
|
|
401
|
+
"skill-family-contracts": { version: KIT_VERSION, path: null, sha256: null },
|
|
402
402
|
"skill-family-engineering-kit": { version: KIT_VERSION, path: null, sha256: null },
|
|
403
|
-
"skill-family-harness-node": { version:
|
|
403
|
+
"skill-family-harness-node": { version: KIT_VERSION, path: null, sha256: null },
|
|
404
404
|
},
|
|
405
405
|
},
|
|
406
406
|
adopted_at: null,
|
|
@@ -413,7 +413,7 @@ function renderProjectProfile({ projectId, profileId }) {
|
|
|
413
413
|
note: "示例覆盖:项目自加严词元告警阈值(基线 2000,取值必须严格小于基线)。覆盖只允许更严、不允许放松;每条覆盖都是可机器校验的声明,按冻结的规则基线目录(rule-baseline-catalog.json)核验。",
|
|
414
414
|
},
|
|
415
415
|
],
|
|
416
|
-
note: "采用声明与自加严覆盖由项目自行维护;engineering kit 只种子化模板,永不覆盖。null 字段必须由项目以真实事实补齐后才构成有效声明:foundation_profile.version/stability 是人的决策;foundation_pin.packages.*.path/sha256 必须从项目写集内真实钉扎工件的字节计算(GK-4 摘要纪律);adopted_at 是完成采用的 RFC 3339 时间。本文件是项目级声明(kind skill-family.project-profile),不是 Profile SPI descriptor;adoption/overrides 段的形状与 SPI
|
|
416
|
+
note: "采用声明与自加严覆盖由项目自行维护;engineering kit 只种子化模板,永不覆盖。null 字段必须由项目以真实事实补齐后才构成有效声明:foundation_profile.version/stability 是人的决策;foundation_pin.packages.*.path/sha256 必须从项目写集内真实钉扎工件的字节计算(GK-4 摘要纪律);adopted_at 是完成采用的 RFC 3339 时间。本文件是项目级声明(kind skill-family.project-profile),不是 Profile SPI descriptor;adoption/overrides 段的形状与 SPI v3 权威定义(Contracts profile-adoption-declaration 的 $defs)逐字段一致。",
|
|
417
417
|
});
|
|
418
418
|
}
|
|
419
419
|
|