@qijenchen/design-system 0.1.0-beta.105 → 0.1.0-beta.106
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/patterns/element-anatomy/item-anatomy.js +1 -1
- package/ds-canonical/fork/consumer/lock.json +4 -6
- package/ds-canonical/fork/governance.lock +1 -17
- package/ds-canonical/fork/manifest.json +10 -14
- package/ds-canonical/hooks/check_audit_post_report_validator.sh +22 -1
- package/ds-canonical/hooks/stop_self_audit.sh +24 -13
- package/ds-canonical/hooks/tests/test_check_audit_post_report_validator.sh +58 -4
- package/ds-canonical/hooks/tests/test_stop_self_audit.sh +53 -15
- package/ds-canonical/skills/codify-principle/SKILL.md +1 -1
- package/ds-canonical/skills/design-system-audit/SKILL.md +1 -0
- package/ds-canonical/skills/design-system-audit/references/audit-prompts.md +4 -4
- package/package.json +1 -1
- package/ds-canonical/fork/consumer/managed-files/6929a3c8bb4d69c507720b4754aeb339c6cbb8ff655e0a381d76606d64695e38.blob +0 -393
- package/ds-canonical/fork/consumer/managed-files/e1b11160d6c4043002d2cbff6d9a4ae551698a96137773c4244dadfac152fcb0.blob +0 -222
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { cva } from "class-variance-authority";
|
|
4
|
-
import {
|
|
4
|
+
import { cn, lineClampClass } from "../../lib/utils.js";
|
|
5
5
|
import { Avatar } from "../../components/Avatar/avatar.js";
|
|
6
6
|
import { Tooltip, TooltipTrigger, TooltipContent } from "../../components/Tooltip/tooltip.js";
|
|
7
7
|
import { useTruncated } from "../../hooks/use-truncated.js";
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
"schemaVersion": 1,
|
|
4
4
|
"role": "template-consumer",
|
|
5
5
|
"release": {
|
|
6
|
-
"designSystem": "0.1.0-beta.
|
|
7
|
-
"storybookConfig": "0.1.0-beta.
|
|
6
|
+
"designSystem": "0.1.0-beta.106",
|
|
7
|
+
"storybookConfig": "0.1.0-beta.106"
|
|
8
8
|
},
|
|
9
9
|
"upgradeTrust": {
|
|
10
10
|
"schemaVersion": 1,
|
|
@@ -42,10 +42,10 @@
|
|
|
42
42
|
"implementationSha256": "98b74118fb5b410b77ad2994aa9105fa7609c7353822f8c5213cce54d94f0af4"
|
|
43
43
|
},
|
|
44
44
|
"payload": {
|
|
45
|
-
"forkCorpusLockSha256": "
|
|
45
|
+
"forkCorpusLockSha256": "f192cdd2cdfcd7e887ac1caeb2ee57410f7419487237fb2702c44b2a1797d671",
|
|
46
46
|
"discoveryPolicySha256": "fba289aeeb5a3f11139b9589f7ab08b5e8c8799c7bdf8f0ea57ebfeb8e7c9da6",
|
|
47
47
|
"providerMaterializersSha256": "732e85b01a12e7dd19469e26e4427f0e0cfe5117a78fc9f69c2f0f89702f463a",
|
|
48
|
-
"providerLifecycleSha256": "
|
|
48
|
+
"providerLifecycleSha256": "6adab7f6bfa9a6a185cfef41bcb2a596d00ef81b439a1052940ec8e0f02616f3",
|
|
49
49
|
"providerInventorySha256": "00adf1c4f6b3367055a9cc3fdfe658660771b4c9931f696e12761a95076ad92c",
|
|
50
50
|
"executionRuntimeSha256": "3fca4ba4094e855412a22308e13f2b5f91dcfeb009512eb8318b3bd1bace8d9a",
|
|
51
51
|
"commonInstructionSha256": "3d40f4b5d3ea78f6c9ca5bd18daa531dc758d77df21a5e7ef35a3336cd71f906",
|
|
@@ -91,7 +91,6 @@
|
|
|
91
91
|
".devcontainer/onboard-banner.sh": "fd87b372e48ff079afb0b257c5e5c28fcf58637b2589a508987460a66c2b8b75",
|
|
92
92
|
".devcontainer/post-create.mjs": "6bc05d2597a54e8f6a5a087901e92ccc89acc3d5873d96ad20814f8f539a146b",
|
|
93
93
|
".github/workflows/audit.yml": "3cb259c8d83d8d378a6d282b0cde0a8884d96c51cf49e655405c3e416e646726",
|
|
94
|
-
".github/workflows/governance-anchor.yml": "3e280e67fd72886b0686a6d296947ee3270b4899e9914c1e3a9c61c951dcc2ee",
|
|
95
94
|
".npmrc": "79eff2d98653857c11ab6a0354fa62685a72ec214545e7fdfabc450e91b7ba63",
|
|
96
95
|
"governance/consumer-governance.md": "bc58561386e0df62306a5d7246e51647847732efa3de84426bada9978ff6da5c",
|
|
97
96
|
"governance/provider-cli-toolchain.json": "6bc607cb6ac299b332d35ca871c838b54e367d96775ad0e759c827a0e3ca10cc",
|
|
@@ -104,7 +103,6 @@
|
|
|
104
103
|
"scripts/audit-consumer-a11y.mjs": "a2ca30c164ef55277cc783a79450eb1bff203b936acbfad3bc94a8c644cfd173",
|
|
105
104
|
"scripts/check-plugin-installed.mjs": "f2a673cc011743fe51f87746b9aebbd3332378cd2732088eda621e16a3c74386",
|
|
106
105
|
"scripts/consumer-source-harness.mjs": "725072155d0b15b373bf6befb333cd2085dc8a38fff8538b4f4c8ea56f9c5308",
|
|
107
|
-
"scripts/governance-anchor-preflight.mjs": "3f87bcdcf95f85c73351646a5f818b088d6528cc6bd3c811054087fd58b4fdfa",
|
|
108
106
|
"scripts/lib/a11y-static-server.mjs": "3ae8ca74a66ca5f155d1a1d0c97280d41f07556274c8c077705a489ebe6a7018",
|
|
109
107
|
"scripts/lib/canonical-path-containment.mjs": "0244e31dc3be5015bed3efd65a5a77086a2e4676e9ac79cd0801c8cde6a74fcd",
|
|
110
108
|
"scripts/lib/closed-tool-execution.mjs": "131255a302f1b403fe95dd6251e180d7f3ce920faf6cbfe90496b52d192d93c3",
|
|
@@ -396,14 +396,6 @@
|
|
|
396
396
|
"classification": null,
|
|
397
397
|
"destination": "scripts/product-template-scaffold-lock.mjs"
|
|
398
398
|
},
|
|
399
|
-
{
|
|
400
|
-
"schemaVersion": 1,
|
|
401
|
-
"file": "consumer/managed-files/6929a3c8bb4d69c507720b4754aeb339c6cbb8ff655e0a381d76606d64695e38.blob",
|
|
402
|
-
"sha256": "3f87bcdcf95f85c73351646a5f818b088d6528cc6bd3c811054087fd58b4fdfa",
|
|
403
|
-
"source": "scripts/governance-anchor-preflight.mjs",
|
|
404
|
-
"classification": null,
|
|
405
|
-
"destination": "scripts/governance-anchor-preflight.mjs"
|
|
406
|
-
},
|
|
407
399
|
{
|
|
408
400
|
"schemaVersion": 1,
|
|
409
401
|
"file": "consumer/managed-files/6b746628ac11fcccbe277ef6fea94cc2a7b90ea2822e83536eae80d5ee4b3fa5.blob",
|
|
@@ -564,14 +556,6 @@
|
|
|
564
556
|
"classification": null,
|
|
565
557
|
"destination": ".devcontainer/onboard-banner.sh"
|
|
566
558
|
},
|
|
567
|
-
{
|
|
568
|
-
"schemaVersion": 1,
|
|
569
|
-
"file": "consumer/managed-files/e1b11160d6c4043002d2cbff6d9a4ae551698a96137773c4244dadfac152fcb0.blob",
|
|
570
|
-
"sha256": "3e280e67fd72886b0686a6d296947ee3270b4899e9914c1e3a9c61c951dcc2ee",
|
|
571
|
-
"source": "template/ds-product-template/.github/workflows/governance-anchor.yml",
|
|
572
|
-
"classification": null,
|
|
573
|
-
"destination": ".github/workflows/governance-anchor.yml"
|
|
574
|
-
},
|
|
575
559
|
{
|
|
576
560
|
"schemaVersion": 1,
|
|
577
561
|
"file": "consumer/managed-files/e26057408661597aac4a57cdf7e30e2f9d0afb1b9a2dbbb692c3805cd9028ca5.blob",
|
|
@@ -919,7 +903,7 @@
|
|
|
919
903
|
{
|
|
920
904
|
"schemaVersion": 1,
|
|
921
905
|
"file": "manifest.json",
|
|
922
|
-
"sha256": "
|
|
906
|
+
"sha256": "d92cc77eb725aadb92add11e63cc3e366886949f0535825a97e60d1571998a66",
|
|
923
907
|
"source": null,
|
|
924
908
|
"classification": null,
|
|
925
909
|
"destination": null
|
|
@@ -104,20 +104,20 @@
|
|
|
104
104
|
},
|
|
105
105
|
"providerLifecycle": {
|
|
106
106
|
"schemaVersion": 4,
|
|
107
|
-
"releaseVersion": "0.1.0-beta.
|
|
107
|
+
"releaseVersion": "0.1.0-beta.106",
|
|
108
108
|
"immutableHead": {
|
|
109
109
|
"providerInventorySha256": "86e9d0668cb54ad1bf05c36e78b4aa80417d5f468c929dd3ddcf69c1afbf32e4",
|
|
110
|
-
"releaseVersion": "0.1.0-beta.
|
|
111
|
-
"snapshotSha256": "
|
|
110
|
+
"releaseVersion": "0.1.0-beta.105",
|
|
111
|
+
"snapshotSha256": "e0a8c02e4faeb126f3612eb1559e6bce02b408ffabcd8f0101b9c005ea1344a1"
|
|
112
112
|
},
|
|
113
|
-
"ledgerSha256": "
|
|
114
|
-
"previousSnapshotSha256": "
|
|
115
|
-
"currentSnapshotSha256": "
|
|
113
|
+
"ledgerSha256": "7096ad8e6d068b37dca410a01bf0af73cc577a98a6cbdc97135477cd0c24ade7",
|
|
114
|
+
"previousSnapshotSha256": "e0a8c02e4faeb126f3612eb1559e6bce02b408ffabcd8f0101b9c005ea1344a1",
|
|
115
|
+
"currentSnapshotSha256": "6ba66c4b02e1985cc2175f94a481eb02cf1be57281d9c6b08ce39d6a7c8243c1",
|
|
116
116
|
"currentProviderInventorySha256": "86e9d0668cb54ad1bf05c36e78b4aa80417d5f468c929dd3ddcf69c1afbf32e4",
|
|
117
117
|
"retiredProviders": [],
|
|
118
118
|
"currentSnapshot": {
|
|
119
|
-
"releaseVersion": "0.1.0-beta.
|
|
120
|
-
"previousSnapshotSha256": "
|
|
119
|
+
"releaseVersion": "0.1.0-beta.106",
|
|
120
|
+
"previousSnapshotSha256": "e0a8c02e4faeb126f3612eb1559e6bce02b408ffabcd8f0101b9c005ea1344a1",
|
|
121
121
|
"providers": [
|
|
122
122
|
{
|
|
123
123
|
"id": "claude",
|
|
@@ -193,8 +193,8 @@
|
|
|
193
193
|
"retiredProviders": []
|
|
194
194
|
},
|
|
195
195
|
"immutableHeadSnapshot": {
|
|
196
|
-
"releaseVersion": "0.1.0-beta.
|
|
197
|
-
"previousSnapshotSha256": "
|
|
196
|
+
"releaseVersion": "0.1.0-beta.105",
|
|
197
|
+
"previousSnapshotSha256": "3d9d69c770e9f8bfb9b99e795598178ba324af8d89ca809e09aef4569cfeef31",
|
|
198
198
|
"providers": [
|
|
199
199
|
{
|
|
200
200
|
"id": "claude",
|
|
@@ -1848,7 +1848,6 @@
|
|
|
1848
1848
|
".devcontainer/onboard-banner.sh": "consumer/managed-files/da2ad2737315d25a5300905ddefd62f1695056a8a1af155c5b34170ad9923bce.blob",
|
|
1849
1849
|
".devcontainer/post-create.mjs": "consumer/managed-files/3ddedc67b464bdfe3dde9803a2d917920c96d633678b802eee638a44cd47366a.blob",
|
|
1850
1850
|
".github/workflows/audit.yml": "consumer/managed-files/93a89f16f6717d1e4e4e27ec20ef916e3355c7aa890ca74cd9b12db30d8a899f.blob",
|
|
1851
|
-
".github/workflows/governance-anchor.yml": "consumer/managed-files/e1b11160d6c4043002d2cbff6d9a4ae551698a96137773c4244dadfac152fcb0.blob",
|
|
1852
1851
|
".npmrc": "consumer/managed-files/e813096d69c49812e40e00be9ac8fa14d77f0ec1f97ab4c45cb096b3bedd35de.blob",
|
|
1853
1852
|
"governance/consumer-governance.md": "consumer/managed-files/884721550b56308812b20917adac7a12f36fdb4ecda6b02460a7f5a219c0c966.blob",
|
|
1854
1853
|
"governance/provider-cli-toolchain.json": "consumer/managed-files/189e551431181e4a88783c02130d2787b801ef731c86e35bcde89a7313aa020e.blob",
|
|
@@ -1861,7 +1860,6 @@
|
|
|
1861
1860
|
"scripts/audit-consumer-a11y.mjs": "consumer/managed-files/e71c65f45c6af7ce12b6b3c0d8028f185da5b719e2050c9b2a6b243d41ae4878.blob",
|
|
1862
1861
|
"scripts/check-plugin-installed.mjs": "consumer/managed-files/ea94e914d5c8acb84ad7660e5bda0f01b6ccc8cf96b0f6b1ce048f8938d0f5b5.blob",
|
|
1863
1862
|
"scripts/consumer-source-harness.mjs": "consumer/managed-files/423729840acdd43c812f17c245b871716c6b60f76e4d3e94864d0e34a815bf8d.blob",
|
|
1864
|
-
"scripts/governance-anchor-preflight.mjs": "consumer/managed-files/6929a3c8bb4d69c507720b4754aeb339c6cbb8ff655e0a381d76606d64695e38.blob",
|
|
1865
1863
|
"scripts/lib/a11y-static-server.mjs": "consumer/managed-files/92e4828cdcb96ff0d01daa16f6885f24045a8e72820665650f2a1890dc3a446c.blob",
|
|
1866
1864
|
"scripts/lib/canonical-path-containment.mjs": "consumer/managed-files/faa128c26102415dc5345e68698efbbd28a395c35ee278dceae044cf430692b2.blob",
|
|
1867
1865
|
"scripts/lib/closed-tool-execution.mjs": "consumer/managed-files/2d1ef5617a5ce3c39ad9aae1c42af7d199f9270c0a0ccb490b63d62dbc6913db.blob",
|
|
@@ -1896,7 +1894,6 @@
|
|
|
1896
1894
|
".devcontainer/onboard-banner.sh",
|
|
1897
1895
|
".devcontainer/post-create.mjs",
|
|
1898
1896
|
".github/workflows/audit.yml",
|
|
1899
|
-
".github/workflows/governance-anchor.yml",
|
|
1900
1897
|
".npmrc",
|
|
1901
1898
|
"AGENTS.md",
|
|
1902
1899
|
"CLAUDE.md",
|
|
@@ -1927,7 +1924,6 @@
|
|
|
1927
1924
|
"scripts/audit-consumer-a11y.mjs",
|
|
1928
1925
|
"scripts/check-plugin-installed.mjs",
|
|
1929
1926
|
"scripts/consumer-source-harness.mjs",
|
|
1930
|
-
"scripts/governance-anchor-preflight.mjs",
|
|
1931
1927
|
"scripts/governance-check.mjs",
|
|
1932
1928
|
"scripts/lib/a11y-static-server.mjs",
|
|
1933
1929
|
"scripts/lib/canonical-path-containment.mjs",
|
|
@@ -33,7 +33,7 @@ source "$HOOK_DIR/lib/_provider_paths.sh" 2>/dev/null || {
|
|
|
33
33
|
set -uo pipefail
|
|
34
34
|
|
|
35
35
|
governance_hook_load_input
|
|
36
|
-
governance_hook_require_commands grep node sort wc tr awk
|
|
36
|
+
governance_hook_require_commands grep jq node sort wc tr awk
|
|
37
37
|
TOOL=$(printf '%s' "$INPUT" | jq -r '.tool_name // ""') \
|
|
38
38
|
|| governance_hook_integrity_fail 'audit validator tool name could not be decoded'
|
|
39
39
|
FILE_PATH=$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // ""') \
|
|
@@ -92,6 +92,27 @@ else
|
|
|
92
92
|
REVIEW_MODE=unspecified
|
|
93
93
|
fi
|
|
94
94
|
|
|
95
|
+
# Report prose is a receipt, never waiver authority. The only valid waiver is
|
|
96
|
+
# the exact one-run selection frozen in the current immutable manifest. Reusing
|
|
97
|
+
# the canonical verifier also closes pointer digest, current HEAD/tree/index,
|
|
98
|
+
# provider binding, evidence-tree, and one-provider coverage in one readback.
|
|
99
|
+
if [ "$REVIEW_MODE" = waived ]; then
|
|
100
|
+
WAIVER_PROOF=""
|
|
101
|
+
if [ -f "$PROJECT_DIR/scripts/verify-deep-audit-coverage.mjs" ]; then
|
|
102
|
+
WAIVER_PROOF=$(node "$PROJECT_DIR/scripts/verify-deep-audit-coverage.mjs" \
|
|
103
|
+
--repo-root "$PROJECT_DIR" --json 2>/dev/null || true)
|
|
104
|
+
fi
|
|
105
|
+
if ! printf '%s' "$WAIVER_PROOF" | jq -e '
|
|
106
|
+
.evidenceKind == "deep-audit-coverage-verification"
|
|
107
|
+
and .secondOpinion == "waived-by-user"
|
|
108
|
+
and (.providers.self | type == "string" and length > 0)
|
|
109
|
+
and .providers.peer == null
|
|
110
|
+
' >/dev/null 2>&1; then
|
|
111
|
+
WARNINGS="${WARNINGS}\n 🔴 [R] Report 文字不是 second-opinion waiver authority:目前 active immutable run 無可驗證的 exact user waiver/current binding/single-provider coverage。"
|
|
112
|
+
CRITICAL_FAIL=1
|
|
113
|
+
fi
|
|
114
|
+
fi
|
|
115
|
+
|
|
95
116
|
if [ "$REVIEW_MODE" = required ]; then
|
|
96
117
|
if ! printf '%s' "$PEER_PROVIDER" | grep -qE '^[a-z][a-z0-9-]*$'; then
|
|
97
118
|
WARNINGS="${WARNINGS}\n 🔴 [R] Independent review 已被 report 要求/使用,但無 registry-resolved peer provider context。補齊可驗證的 peer 執行與回執,或記錄 user 明確豁免。"
|
|
@@ -64,20 +64,31 @@ PEER_DISCOVERY_MARKERS_JSON="${GOVERNANCE_PEER_DISCOVERY_MARKERS_JSON:-[]}"
|
|
|
64
64
|
INSTRUCTION_ENTRY="${GOVERNANCE_INSTRUCTION_ENTRY:-AGENTS.md}"
|
|
65
65
|
SHARED_INSTRUCTION_ENTRY="${GOVERNANCE_SHARED_INSTRUCTION_ENTRY:-AGENTS.md}"
|
|
66
66
|
|
|
67
|
-
if ! printf '%s' "$SELF_PROVIDER" | grep -qE '^[a-z][a-z0-9-]*$'
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
if ! printf '%s' "$SELF_PROVIDER" | grep -qE '^[a-z][a-z0-9-]*$'; then
|
|
68
|
+
governance_hook_integrity_fail 'self-audit registry-resolved self runtime context is missing or invalid'
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
PEER_CONTEXT_PRESENT=0
|
|
72
|
+
if [ -n "$PEER_PROVIDER$PEER_CLI$PEER_LOCAL_EXECUTABLE$PEER_REPLY_PREFIX" ] \
|
|
73
|
+
|| [ "$PEER_DISCOVERY_MARKERS_JSON" != '[]' ]; then
|
|
74
|
+
PEER_CONTEXT_PRESENT=1
|
|
75
|
+
if ! printf '%s' "$PEER_PROVIDER" | grep -qE '^[a-z][a-z0-9-]*$' \
|
|
76
|
+
|| ! printf '%s' "$PEER_CLI" | grep -qE '^[a-z][a-z0-9-]*$' \
|
|
77
|
+
|| ! printf '%s' "$PEER_REPLY_PREFIX" | grep -qE '^[a-z][a-z0-9-]*-reply$'; then
|
|
78
|
+
governance_hook_integrity_fail 'self-audit registry-resolved peer runtime context is partial or invalid'
|
|
79
|
+
fi
|
|
72
80
|
fi
|
|
73
81
|
|
|
74
82
|
json_array_to_ere() {
|
|
75
83
|
node -- "$HOOK_DIR/lib/provider-marker-regex.mjs" discovery "$1" 2>/dev/null
|
|
76
84
|
}
|
|
77
85
|
|
|
78
|
-
PEER_DISCOVERY_RE
|
|
79
|
-
|
|
80
|
-
|
|
86
|
+
PEER_DISCOVERY_RE=""
|
|
87
|
+
if [ "$PEER_CONTEXT_PRESENT" = "1" ]; then
|
|
88
|
+
PEER_DISCOVERY_RE=$(json_array_to_ere "$PEER_DISCOVERY_MARKERS_JSON") || {
|
|
89
|
+
governance_hook_integrity_fail 'self-audit registry-resolved peer discovery metadata is missing or invalid'
|
|
90
|
+
}
|
|
91
|
+
fi
|
|
81
92
|
|
|
82
93
|
WARNINGS=""
|
|
83
94
|
|
|
@@ -188,8 +199,7 @@ fi
|
|
|
188
199
|
# verify-deep-audit-coverage.mjs = 機械閘;宣告完成 + ledger 有缺口 → BLOCK。
|
|
189
200
|
run_deep_audit_coverage_verify() {
|
|
190
201
|
node scripts/verify-deep-audit-coverage.mjs \
|
|
191
|
-
--self-provider "$SELF_PROVIDER"
|
|
192
|
-
--peer-provider "$PEER_PROVIDER"
|
|
202
|
+
--self-provider "$SELF_PROVIDER"
|
|
193
203
|
}
|
|
194
204
|
if [ -n "$LAST_ASSISTANT" ] && [ -f scripts/verify-deep-audit-coverage.mjs ]; then
|
|
195
205
|
DEEPAUDIT_CLAIM_RE='(deep.?audit.{0,10}(完成|完整|done)|稽核.{0,8}(完整完美|全部完成|都.?跑完)|91.{0,6}dim.{0,8}(都|全).{0,4}(跑|過|完|有證據)|Phase A.{0,6}完成)'
|
|
@@ -197,6 +207,7 @@ if [ -n "$LAST_ASSISTANT" ] && [ -f scripts/verify-deep-audit-coverage.mjs ]; th
|
|
|
197
207
|
if grep -qE "$DEEPAUDIT_CLAIM_RE" <<< "$LAST_ASSISTANT" \
|
|
198
208
|
&& ! grep -qE "$DEEPAUDIT_NEG_RE" <<< "$LAST_ASSISTANT"; then
|
|
199
209
|
if ! run_deep_audit_coverage_verify >/dev/null 2>&1; then
|
|
210
|
+
WARNINGS="${WARNINGS}\n • Deep-audit completion claim rejected:the active immutable run's provider set, 91 dimensions, or component evidence is incomplete."
|
|
200
211
|
CRITICAL_DEEPAUDIT_COVERAGE=1
|
|
201
212
|
fi
|
|
202
213
|
fi
|
|
@@ -254,7 +265,7 @@ done
|
|
|
254
265
|
# (c) Read against peer-cited code path(`packages/design-system/src/...`)
|
|
255
266
|
# (d) Explicit retract phrase(撤回 / 未採納 / 不採用 / skip peer)
|
|
256
267
|
# 任一 missing → CRITICAL 通知;同 Mechanism 1 升 BLOCKER 阻 turn 結束。
|
|
257
|
-
if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ] && [ "$LAST_USER_LINE" -gt 0 ]; then
|
|
268
|
+
if [ "$PEER_CONTEXT_PRESENT" = "1" ] && [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ] && [ "$LAST_USER_LINE" -gt 0 ]; then
|
|
258
269
|
THIS_TURN_FULL=$(tail -n +$((LAST_USER_LINE+1)) "$TRANSCRIPT_PATH" 2>/dev/null)
|
|
259
270
|
PEER_REPLY_READ=$(grep -cE "${PEER_REPLY_PREFIX}-[a-zA-Z0-9_-]+\\.md" <<< "$THIS_TURN_FULL" 2>/dev/null)
|
|
260
271
|
PEER_REPLY_READ=${PEER_REPLY_READ:-0}
|
|
@@ -451,7 +462,7 @@ fi
|
|
|
451
462
|
# ── Mechanism 5: Peer transport discovery gap(2026-05-17 user-authorized)──
|
|
452
463
|
# Registry 宣告 peer 的 local/global discovery markers。本 mechanism 偵測:本 turn 含 active peer
|
|
453
464
|
# collaboration 意圖 AND 無任一 discovery marker trace AND 無撤回 → BLOCKER。
|
|
454
|
-
if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ] && [ "${LAST_USER_LINE:-0}" -gt 0 ]; then
|
|
465
|
+
if [ "$PEER_CONTEXT_PRESENT" = "1" ] && [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ] && [ "${LAST_USER_LINE:-0}" -gt 0 ]; then
|
|
455
466
|
THIS_TURN_RAW=$(tail -n +$((LAST_USER_LINE+1)) "$TRANSCRIPT_PATH" 2>/dev/null)
|
|
456
467
|
# Scope to assistant prose so commit messages, tool inputs, and historical citations do not
|
|
457
468
|
# trigger the gate. Active intent requires an action verb adjacent to the resolved peer id.
|
|
@@ -679,7 +690,7 @@ if [ "${CRITICAL_DEEPAUDIT_COVERAGE:-0}" = "1" ] && [ -n "$LAST_ASSISTANT" ]; th
|
|
|
679
690
|
COVERAGE_OUTPUT=$(run_deep_audit_coverage_verify 2>/dev/null || true)
|
|
680
691
|
GAPLINE=$(awk '/個 dim-證據缺口/ && !found { print; found=1 }' <<< "$COVERAGE_OUTPUT")
|
|
681
692
|
REASON=$(printf '%s' \
|
|
682
|
-
"🚨 DEEP-AUDIT-COVERAGE BLOCKER(M7c,2026-07-12 user-authorized「不抽樣不偷懶…現在和未來可以確保嗎」):你宣告 deep audit 完成,但 neutral evidence verifier 未通過 — ${GAPLINE:-91 dim
|
|
693
|
+
"🚨 DEEP-AUDIT-COVERAGE BLOCKER(M7c,2026-07-12 user-authorized「不抽樣不偷懶…現在和未來可以確保嗎」):你宣告 deep audit 完成,但 neutral evidence verifier 未通過 — ${GAPLINE:-immutable run 的 91 dim / component 證據未閉合}。禁宣稱 done。跑 node scripts/verify-deep-audit-coverage.mjs --self-provider ${SELF_PROVIDER} 看缺哪些;provider set 與本次 second-opinion disposition 只由 active immutable manifest 決定。填完 Git-owned governance-runtime/evidence/deep-audit 內的 judgment / deterministic / hook-residue / ci-enforced / component-a1b 證據才可宣稱;OR 明寫『核心做完,dim 覆蓋待補』。否則 turn 不結束。" \
|
|
683
694
|
"本機制起因:本 session AI 漏跑 91 dim 裡 deterministic 24 + judgment 26 + hook 39 + CI 2 四層,只做 claim-vs-code + 反應式補,user 憤怒。觸發器 = 宣告 deep audit 完成本身。")
|
|
684
695
|
emit_governance_block "$REASON"
|
|
685
696
|
fi
|
|
@@ -21,7 +21,34 @@ TMPDIR_TEST=$(mktemp -d)
|
|
|
21
21
|
trap 'rm -rf "$TMPDIR_TEST"' EXIT
|
|
22
22
|
mkdir -p "$TMPDIR_TEST/.claude/logs"
|
|
23
23
|
mkdir -p "$TMPDIR_TEST/corpus/packages/governance/canonical" \
|
|
24
|
-
"$TMPDIR_TEST/corpus/canonical/skills/design-system-audit/references"
|
|
24
|
+
"$TMPDIR_TEST/corpus/canonical/skills/design-system-audit/references" \
|
|
25
|
+
"$TMPDIR_TEST/scripts"
|
|
26
|
+
cat > "$TMPDIR_TEST/scripts/verify-deep-audit-coverage.mjs" <<'EOF'
|
|
27
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
28
|
+
import { resolve } from 'node:path'
|
|
29
|
+
|
|
30
|
+
const rootIndex = process.argv.indexOf('--repo-root')
|
|
31
|
+
const root = rootIndex >= 0 ? process.argv[rootIndex + 1] : process.cwd()
|
|
32
|
+
const modePath = resolve(root, '.fixture-review-mode')
|
|
33
|
+
const mode = existsSync(modePath) ? readFileSync(modePath, 'utf8').trim() : 'missing'
|
|
34
|
+
if (mode === 'waived') {
|
|
35
|
+
process.stdout.write(JSON.stringify({
|
|
36
|
+
evidenceKind: 'deep-audit-coverage-verification',
|
|
37
|
+
secondOpinion: 'waived-by-user',
|
|
38
|
+
providers: { self: 'alpha', peer: null },
|
|
39
|
+
}))
|
|
40
|
+
process.exit(0)
|
|
41
|
+
}
|
|
42
|
+
if (mode === 'selected') {
|
|
43
|
+
process.stdout.write(JSON.stringify({
|
|
44
|
+
evidenceKind: 'deep-audit-coverage-verification',
|
|
45
|
+
secondOpinion: 'completed',
|
|
46
|
+
providers: { self: 'alpha', peer: 'beta' },
|
|
47
|
+
}))
|
|
48
|
+
process.exit(0)
|
|
49
|
+
}
|
|
50
|
+
process.exit(2)
|
|
51
|
+
EOF
|
|
25
52
|
cat > "$TMPDIR_TEST/corpus/packages/governance/canonical/providers.json" <<'EOF'
|
|
26
53
|
{
|
|
27
54
|
"canonical": {"roots": {"skills": "canonical/skills"}},
|
|
@@ -264,8 +291,8 @@ else
|
|
|
264
291
|
FAIL=$((FAIL+1)); FAILED_TESTS="${FAILED_TESTS}\n - 8. future peer verdict"
|
|
265
292
|
fi
|
|
266
293
|
|
|
267
|
-
# 9.
|
|
268
|
-
#
|
|
294
|
+
# 9. Report prose alone cannot authorize a waiver. The same receipt passes only
|
|
295
|
+
# when the current immutable run verifier proves the exact one-run waiver.
|
|
269
296
|
unset GOVERNANCE_PEER_PROVIDER GOVERNANCE_PEER_DISPLAY_NAME
|
|
270
297
|
cat > "$TMPDIR_TEST/.claude/logs/deep-audit-test/C1-final-report.md" <<'EOF'
|
|
271
298
|
# Deep Audit 報告
|
|
@@ -280,7 +307,34 @@ second opinion: waived by user
|
|
|
280
307
|
- 設計語言 fit:符合 density canonical
|
|
281
308
|
EOF
|
|
282
309
|
run_hook "$TMPDIR_TEST/.claude/logs/deep-audit-test/C1-final-report.md" "Write"
|
|
283
|
-
|
|
310
|
+
expect_block_stderr "9. text-only user waiver → block" "Report 文字不是 second-opinion waiver authority"
|
|
311
|
+
|
|
312
|
+
printf 'waived\n' > "$TMPDIR_TEST/.fixture-review-mode"
|
|
313
|
+
run_hook "$TMPDIR_TEST/.claude/logs/deep-audit-test/C1-final-report.md" "Write"
|
|
314
|
+
expect_silent "9b. immutable user-waived run → no peer context required"
|
|
315
|
+
|
|
316
|
+
printf 'selected\n' > "$TMPDIR_TEST/.fixture-review-mode"
|
|
317
|
+
run_hook "$TMPDIR_TEST/.claude/logs/deep-audit-test/C1-final-report.md" "Write"
|
|
318
|
+
expect_block_stderr "9c. selected run cannot be waived by report prose" "Report 文字不是 second-opinion waiver authority"
|
|
319
|
+
|
|
320
|
+
printf 'tampered\n' > "$TMPDIR_TEST/.fixture-review-mode"
|
|
321
|
+
run_hook "$TMPDIR_TEST/.claude/logs/deep-audit-test/C1-final-report.md" "Write"
|
|
322
|
+
expect_block_stderr "9d. tampered/missing active waiver proof → block" "Report 文字不是 second-opinion waiver authority"
|
|
323
|
+
|
|
324
|
+
printf 'waived\n' > "$TMPDIR_TEST/.fixture-review-mode"
|
|
325
|
+
cat > "$TMPDIR_TEST/.claude/logs/deep-audit-test/C1-final-report.md" <<'EOF'
|
|
326
|
+
# Deep Audit 報告
|
|
327
|
+
Dim 1: pass
|
|
328
|
+
|
|
329
|
+
### 待你拍板
|
|
330
|
+
1. 決策一:Popover 內距是否改 12px
|
|
331
|
+
- SSOT 理由:改跨元件 canonical 視覺結構
|
|
332
|
+
- SSOT-check:grep spec + memory,無既有拍板
|
|
333
|
+
- 世界級:Polaris https://polaris.shopify.com/tokens/space / Material / Ant(3 家)
|
|
334
|
+
- 設計語言 fit:符合 density canonical
|
|
335
|
+
EOF
|
|
336
|
+
run_hook "$TMPDIR_TEST/.claude/logs/deep-audit-test/C1-final-report.md" "Write"
|
|
337
|
+
expect_block_stderr "9e. active waiver still requires explicit report receipt" "registry-resolved peer provider"
|
|
284
338
|
|
|
285
339
|
# 10. A general report with no decision and an explicit task-level receipt also
|
|
286
340
|
# has no peer dependency.
|
|
@@ -27,19 +27,21 @@ EVIDENCE_ROOT="$GIT_DIR/governance-runtime/evidence"
|
|
|
27
27
|
cp "$REPO_ROOT/scripts/lib/governance-runtime-evidence.mjs" "$PROJECT_DIR/scripts/lib/governance-runtime-evidence.mjs"
|
|
28
28
|
cp "$REPO_ROOT/packages/governance/src/closed-tool-execution.mjs" "$PROJECT_DIR/scripts/lib/closed-tool-execution.mjs"
|
|
29
29
|
cat > "$PROJECT_DIR/scripts/verify-deep-audit-coverage.mjs" <<'VERIFY_FIXTURE'
|
|
30
|
-
import { existsSync } from 'node:fs'
|
|
30
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
31
31
|
import { resolve } from 'node:path'
|
|
32
32
|
|
|
33
33
|
const selfIndex = process.argv.indexOf('--self-provider')
|
|
34
34
|
const peerIndex = process.argv.indexOf('--peer-provider')
|
|
35
|
-
const
|
|
36
|
-
&& process.argv[selfIndex + 1] === 'alpha'
|
|
37
|
-
&& process.argv[peerIndex + 1] === 'beta'
|
|
35
|
+
const selfIsExact = selfIndex >= 0 && process.argv[selfIndex + 1] === 'alpha'
|
|
38
36
|
const stateRoot = process.env.GOVERNANCE_STATE_DIR
|
|
39
37
|
const neutralReceipt = stateRoot
|
|
40
38
|
? resolve(stateRoot, '..', 'evidence/deep-audit/fixture-complete.json')
|
|
41
39
|
: null
|
|
42
|
-
|
|
40
|
+
let receipt = null
|
|
41
|
+
try { receipt = neutralReceipt && existsSync(neutralReceipt) ? JSON.parse(readFileSync(neutralReceipt, 'utf8')) : null } catch {}
|
|
42
|
+
const immutableProviderClosure = receipt?.secondOpinion === 'waived-by-user'
|
|
43
|
+
|| (receipt?.secondOpinion === 'completed' && receipt?.peerEvidence === true)
|
|
44
|
+
process.exit(selfIsExact && peerIndex === -1 && immutableProviderClosure ? 0 : 1)
|
|
43
45
|
VERIFY_FIXTURE
|
|
44
46
|
{
|
|
45
47
|
echo 'const DIMS = {'
|
|
@@ -71,25 +73,34 @@ export GOVERNANCE_SHARED_INSTRUCTION_ENTRY=AGENTS.md
|
|
|
71
73
|
|
|
72
74
|
PASS=0; FAIL=0
|
|
73
75
|
run_test() {
|
|
74
|
-
local name="$1"; local expect_block="$2"; local transcript_content="$3"
|
|
76
|
+
local name="$1"; local expect_block="$2"; local transcript_content="$3"; local direct_message="${4:-}"
|
|
75
77
|
local transcript="$TMPDIR/transcript-$$.jsonl"
|
|
76
78
|
printf '%s\n' "$transcript_content" > "$transcript"
|
|
77
79
|
local input
|
|
78
|
-
input=$(
|
|
79
|
-
|
|
80
|
+
input=$(jq -nc --arg path "$transcript" --arg message "$direct_message" \
|
|
81
|
+
'{transcript_path:$path,session_id:"test",last_assistant_message:$message}')
|
|
82
|
+
local stdout stderr_file exit_code
|
|
83
|
+
stderr_file=$(mktemp)
|
|
84
|
+
set +e
|
|
85
|
+
stdout=$(echo "$input" | bash "$HOOK" 2>"$stderr_file")
|
|
86
|
+
exit_code=$?
|
|
87
|
+
set -e
|
|
80
88
|
if [ "$expect_block" = "1" ]; then
|
|
81
|
-
if echo "$stdout" | grep -q '"governanceDecision":"block"'; then
|
|
89
|
+
if [ "$exit_code" = "0" ] && echo "$stdout" | grep -q '"governanceDecision":"block"'; then
|
|
82
90
|
echo " PASS $name (blocked as expected)"; PASS=$((PASS+1))
|
|
83
91
|
else
|
|
84
|
-
echo " FAIL $name (expected block, got: ${stdout:0:120})"; FAIL=$((FAIL+1))
|
|
92
|
+
echo " FAIL $name (expected block, exit=$exit_code, got: ${stdout:0:120}, stderr: $(head -c 160 "$stderr_file"))"; FAIL=$((FAIL+1))
|
|
85
93
|
fi
|
|
86
94
|
else
|
|
87
|
-
if
|
|
95
|
+
if [ "$exit_code" != "0" ]; then
|
|
96
|
+
echo " FAIL $name (unexpected exit=$exit_code, stderr: $(head -c 160 "$stderr_file"))"; FAIL=$((FAIL+1))
|
|
97
|
+
elif echo "$stdout" | grep -q '"governanceDecision":"block"'; then
|
|
88
98
|
echo " FAIL $name (unexpected block: ${stdout:0:120})"; FAIL=$((FAIL+1))
|
|
89
99
|
else
|
|
90
100
|
echo " PASS $name (no block)"; PASS=$((PASS+1))
|
|
91
101
|
fi
|
|
92
102
|
fi
|
|
103
|
+
rm -f "$stderr_file"
|
|
93
104
|
}
|
|
94
105
|
|
|
95
106
|
run_exact_io_test() {
|
|
@@ -232,19 +243,46 @@ cp "$PROJECT_DIR/.claude/logs/dim-audit/dim-2.json" "$PROJECT_DIR/.claude/logs/c
|
|
|
232
243
|
printf '%s\n' '{"dim":3,"hook":"poison","residueCount":0,"findings":[]}' > "$PROJECT_DIR/.claude/logs/hook-residue/dim-3.json"
|
|
233
244
|
printf '%s\n' '{"component":"Button","claimsVerified":1,"falseClaims":[]}' > "$PROJECT_DIR/.claude/logs/claude-a1b-deep/Button.json"
|
|
234
245
|
cp "$PROJECT_DIR/.claude/logs/claude-a1b-deep/Button.json" "$PROJECT_DIR/.claude/logs/codex-phaseB/Button.json"
|
|
246
|
+
set +e
|
|
247
|
+
(cd "$PROJECT_DIR" && node scripts/verify-deep-audit-coverage.mjs --self-provider alpha >/dev/null 2>&1)
|
|
248
|
+
VERIFY_FIXTURE_EXIT=$?
|
|
249
|
+
set -e
|
|
250
|
+
[ "$VERIFY_FIXTURE_EXIT" = "1" ] || { echo "FATAL: incomplete verifier fixture must exit 1, got $VERIFY_FIXTURE_EXIT"; exit 1; }
|
|
235
251
|
T10="$USER_MSG
|
|
236
252
|
"'{"message":{"role":"assistant","content":[{"type":"text","text":"deep audit 完成"}]}}'
|
|
237
|
-
run_test "Test 10: poisoned .claude deep-audit ledger → block" 1 "
|
|
253
|
+
run_test "Test 10: poisoned .claude deep-audit ledger → block" 1 "" "deep audit 完成"
|
|
238
254
|
|
|
239
|
-
# Test 11: the hook accepts
|
|
255
|
+
# Test 11: the hook accepts a manifest-authorized one-run waiver after `.claude` deletion,
|
|
256
|
+
# and never forwards adapter-injected ambient peer metadata as a verifier assertion.
|
|
240
257
|
# Full immutable run/envelope closure belongs to scripts/test-verify-deep-audit-coverage.mjs;
|
|
241
258
|
# this focused fixture proves argument wiring and that provider-home poison is never authority.
|
|
242
259
|
mkdir -p "$EVIDENCE_ROOT/deep-audit"
|
|
243
|
-
printf '%s\n' '{"
|
|
260
|
+
printf '%s\n' '{"secondOpinion":"waived-by-user"}' > "$EVIDENCE_ROOT/deep-audit/fixture-complete.json"
|
|
244
261
|
rm -rf "$PROJECT_DIR/.claude"
|
|
245
|
-
run_test "Test 11:
|
|
262
|
+
run_test "Test 11: waived immutable run + ambient peer + deleted .claude → no block" 0 "" "deep audit 完成"
|
|
246
263
|
[ ! -e "$PROJECT_DIR/infra/governance/runtime" ] || { echo " FAIL deep-audit evidence mutated workspace runtime"; FAIL=$((FAIL+1)); }
|
|
247
264
|
|
|
265
|
+
# A waived run remains valid when the adapter has no peer runtime at all.
|
|
266
|
+
unset GOVERNANCE_PEER_PROVIDER GOVERNANCE_PEER_DISPLAY_NAME GOVERNANCE_PEER_CLI \
|
|
267
|
+
GOVERNANCE_PEER_LOCAL_EXECUTABLE GOVERNANCE_PEER_REPLY_ARTIFACT_PREFIX \
|
|
268
|
+
GOVERNANCE_PEER_DISCOVERY_MARKERS_JSON
|
|
269
|
+
run_test "Test 11b: waived immutable run + no peer runtime → no block" 0 "" "deep audit 完成"
|
|
270
|
+
|
|
271
|
+
# Merely switching prose/context to no peer cannot waive a selected run: the
|
|
272
|
+
# immutable verifier still requires the selected peer's complete evidence.
|
|
273
|
+
printf '%s\n' '{"secondOpinion":"completed","peerEvidence":false}' > "$EVIDENCE_ROOT/deep-audit/fixture-complete.json"
|
|
274
|
+
run_test "Test 11c: selected run without peer evidence → block" 1 "" "稽核全部完成"
|
|
275
|
+
printf '%s\n' '{"secondOpinion":"completed","peerEvidence":true}' > "$EVIDENCE_ROOT/deep-audit/fixture-complete.json"
|
|
276
|
+
run_test "Test 11d: selected run with complete peer evidence → no block" 0 "" "deep audit 完成"
|
|
277
|
+
|
|
278
|
+
# Restore the normal registry-resolved peer context for the peer-specific hook tests below.
|
|
279
|
+
export GOVERNANCE_PEER_PROVIDER=beta
|
|
280
|
+
export GOVERNANCE_PEER_DISPLAY_NAME="Beta"
|
|
281
|
+
export GOVERNANCE_PEER_CLI=beta
|
|
282
|
+
export GOVERNANCE_PEER_LOCAL_EXECUTABLE=node_modules/.bin/beta
|
|
283
|
+
export GOVERNANCE_PEER_REPLY_ARTIFACT_PREFIX=beta-reply
|
|
284
|
+
export GOVERNANCE_PEER_DISCOVERY_MARKERS_JSON='["node_modules/.bin/beta","command -v beta"]'
|
|
285
|
+
|
|
248
286
|
# Tests 12-14: inputs larger than a pipe buffer must preserve exact policy and I/O contracts.
|
|
249
287
|
# Put each positive marker at the beginning so a producer→`grep -q` implementation would close
|
|
250
288
|
# the pipe early and reproduce the historical Bash `echo: write error: Broken pipe` failure.
|
|
@@ -107,7 +107,7 @@ Per layer 一個 commit:
|
|
|
107
107
|
Scope / type / affected homes 是工程 mapping,由 agent 依 canonical 自行決定。只有原則文字本身對產品/UI/UX語意有多種合理解讀且會改 SSOT,才請 user 釐清。
|
|
108
108
|
|
|
109
109
|
### Gate 2 — Phase 2 benchmark
|
|
110
|
-
< 3 家世界級對照 → 繼續 primary-source research
|
|
110
|
+
< 3 家世界級對照 → 繼續 primary-source research,證據仍不足就不 codify 並明列 benchmark gap;只有 task/deliverable 明確要求 independent review 且 peer 證據不可得時才標 `REVIEW-BLOCKED`。只有 OG-only 是否成為產品/UI/UX原則的選擇交給 user,工程治理原則由 evidence + security/hard gates 決定。
|
|
111
111
|
|
|
112
112
|
### Gate 3 — Phase 3 per-layer
|
|
113
113
|
每 layer 都過 tests/rollback check;task/deliverable 明確要求時才把 independent review 列為 required。工程 canonical substantive 直接執行,只有產品/UI/UX SSOT 真取捨停下。
|
|
@@ -361,6 +361,7 @@ Phase 1-3 覆蓋 D1+D2;D3-D6 chain 專門 skill。**模式**:高效(default)scop
|
|
|
361
361
|
**Deep mode**(`--deep`):在**同一 run、final report 前**直接執行 `/knowledge-prune` full Phase 0–5;P0/P1/P2E auto-fix,只有 P2H 等 user。不得只排到 next turn、只寫 trigger、或以 report-only 冒充已跑。接著逐 row 讀 `packages/design-system/ds-canonical/references/governance-audit-coverage.md`,把每個 registered governance home/artifact 類型對到本次 deterministic 或 judgment evidence;未掛載外部 repo/缺 telemetry 必標 `UNOBSERVED`,不可寫 PASS。Trigger 條件(2026-05-17 加 4 條,共 9 條):registry-declared instruction budget 超標 / MEMORY > 20 / 動 Meta-Pattern / hook-fires 6 月 0 fire / corrections > 10 / **audit-prompts.md coverage < 100%** / **`@benchmark-unverified-blanket` count > 0** / **new audit dim added 本次** / **hooks count >= canonical soft threshold**。
|
|
362
362
|
|
|
363
363
|
Deep final report 的收斂回執使用固定、可機械驗證的字串:`knowledge-prune: same-run complete`、`governance-coverage: same-run reconciled`(並列出各 `UNOBSERVED`),以及 `second opinion: waived by user`/`second opinion: not required by task`/可驗證的 required peer review 其一。回執缺席時 final report 必須 fail closed,不得把 recovery trigger 當作已完成證據。
|
|
364
|
+
若 user 明確只豁免本次 second opinion,formal run 準備必顯式帶 `--second-opinion-waiver user`,把 exact one-run authority 固定進 immutable manifest;報告文字不能反向替未授權 run 補造 waiver,也不得和任何 peer selection 參數並用。
|
|
364
365
|
|
|
365
366
|
Deep mode 的 full D10 同時依 [runtime evidence retention canonical](../../references/runtime-evidence-retention.md) 枚舉各 observed worktree 的 runtime evidence、保留 live/genuine/unexpired closure,並自主回收可安全判定的 unreferenced blocked/failed/stale whole run;禁止 partial CAS pruning 或新增 user approval ceremony。
|
|
366
367
|
|
|
@@ -1297,13 +1297,13 @@ Report file:line gaps。End:`N onboarding 環節 checked, M gaps`。Don't fix.
|
|
|
1297
1297
|
|
|
1298
1298
|
## 66. Immutable cross-repo promotion + visual parity(CI-ENFORCED,2026-07-25 升級)
|
|
1299
1299
|
|
|
1300
|
-
**Type**: Absolute / **Canonical**: `release.yml`
|
|
1300
|
+
**Type**: Absolute / **Canonical**: protected-main `release.yml` exact-tag build-once + npm Trusted Publishing + GitHub/npm readback + `mirror-to-published-template.yml` + `consumerctl plan/check/apply-fanout` + consumer native `audit.yml / Verify consumer` + `composition-fidelity.yml` + `dogfood-prepublish-verify.mjs` + `scripts/visual-assertions.json` + `ci-evidence-plan.json` + `infra/governance/desired/github.json` / **Home**: scenario-definition.md mirror chain
|
|
1301
1301
|
|
|
1302
1302
|
```
|
|
1303
1303
|
Your job(NO-SAMPLE):驗 protected DS PR merge → attested npm publish → template/fleet exact-version PR → consumer required CI/visual parity 整鏈無斷;任何 direct-main、PAT fallback、mutable tag dependency 都是失敗。
|
|
1304
|
-
**成立條件**:必須讀回受保護主分支的 ruleset
|
|
1305
|
-
Enumerate + Read:release
|
|
1306
|
-
|
|
1304
|
+
**成立條件**:必須讀回受保護主分支的 ruleset、GitHub native `Verify consumer` required check、精確 candidate commit、immutable release/BOM、template/fleet exact-version PR 與 consumer required visual checks,並由封閉 schema 的 CI evidence receipt 綁定同一 frozen deep-audit run。consumer profile 必為零 App environment/零 App verdict producer;只讀 workflow 原始碼、模型判斷或手寫 JSON 不構成通過證據;遠端尚未啟用時必標 pending/fail-closed。
|
|
1305
|
+
Enumerate + Read:protected-main `release.yml` exact-tag build-once/publish/readback → mirror workflow → consumerctl plan/check/apply-fanout →
|
|
1306
|
+
template exact-version PR 的 `audit.yml / Verify consumer` → product-consumer exact-version PR 的同名 native required verdict /
|
|
1307
1307
|
composition-fidelity visual-diff scenarios(全 N scenario)/ dogfood-prepublish / CI evidence plan。對每環節驗 trigger
|
|
1308
1308
|
event allowlist、exact version/BOM/candidate/PR/check readback 綁定、無 PAT scope 漏或 direct-main fallback、visual
|
|
1309
1309
|
parity scenario 覆蓋 stakeholder flow。Report file:line。End:`N 環節, M gaps`。Don't fix.
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"type": "git",
|
|
5
5
|
"url": "git+https://github.com/ajenchen/design-system.git"
|
|
6
6
|
},
|
|
7
|
-
"version": "0.1.0-beta.
|
|
7
|
+
"version": "0.1.0-beta.106",
|
|
8
8
|
"private": false,
|
|
9
9
|
"description": "World-class design system — components, patterns, tokens, hooks (single source of truth for team distribution).",
|
|
10
10
|
"type": "module",
|
|
@@ -1,393 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Base-trusted preflight for pull_request_target. The candidate tree is data:
|
|
3
|
-
// no candidate script or dependency is executed here.
|
|
4
|
-
|
|
5
|
-
import { createHash } from 'node:crypto'
|
|
6
|
-
import { existsSync, lstatSync, readFileSync, readdirSync, readlinkSync, writeFileSync } from 'node:fs'
|
|
7
|
-
import { resolve, join, relative } from 'node:path'
|
|
8
|
-
import { fileURLToPath } from 'node:url'
|
|
9
|
-
import {
|
|
10
|
-
validateUpgradeTrustPolicy,
|
|
11
|
-
verifyUpgradeProvenance,
|
|
12
|
-
} from './verify-upgrade-provenance.mjs'
|
|
13
|
-
import { assertNoRootNpmShrinkwrap } from './lib/governance-dependency-bootstrap.mjs'
|
|
14
|
-
import { compareUtf8Bytes } from './lib/provider-lifecycle.mjs'
|
|
15
|
-
|
|
16
|
-
const EXACT = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/
|
|
17
|
-
const PACKAGES = ['@qijenchen/design-system', '@qijenchen/storybook-config']
|
|
18
|
-
const PRODUCT_WORKSPACES = ['apps/*']
|
|
19
|
-
const PRODUCT_WORKSPACE_LINK = /^node_modules\/@product\/[A-Za-z0-9._-]+$/
|
|
20
|
-
const PRODUCT_WORKSPACE_PATH = /^apps\/[A-Za-z0-9._-]+$/
|
|
21
|
-
const REGISTRY_ORIGIN = 'https://registry.npmjs.org'
|
|
22
|
-
const REGISTRY_RESPONSE_MAX_BYTES = 1024 * 1024
|
|
23
|
-
const REGISTRY_USER_AGENT = 'qijenchen-governance-anchor-preflight/1'
|
|
24
|
-
|
|
25
|
-
function parseVersion(value) {
|
|
26
|
-
const match = String(value || '').match(EXACT)
|
|
27
|
-
if (!match) throw new Error(`exact semver required; got ${JSON.stringify(value)}`)
|
|
28
|
-
return { core: match.slice(1, 4).map(Number), pre: match[4]?.split('.') ?? null }
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function compareVersion(a, b) {
|
|
32
|
-
const left = parseVersion(a)
|
|
33
|
-
const right = parseVersion(b)
|
|
34
|
-
for (let i = 0; i < 3; i += 1) if (left.core[i] !== right.core[i]) return left.core[i] - right.core[i]
|
|
35
|
-
if (left.pre === null || right.pre === null) return left.pre === right.pre ? 0 : left.pre === null ? 1 : -1
|
|
36
|
-
for (let i = 0; i < Math.max(left.pre.length, right.pre.length); i += 1) {
|
|
37
|
-
if (left.pre[i] === undefined || right.pre[i] === undefined) return left.pre[i] === undefined ? -1 : 1
|
|
38
|
-
if (left.pre[i] === right.pre[i]) continue
|
|
39
|
-
const an = /^\d+$/.test(left.pre[i])
|
|
40
|
-
const bn = /^\d+$/.test(right.pre[i])
|
|
41
|
-
if (an && bn) return Number(left.pre[i]) - Number(right.pre[i])
|
|
42
|
-
if (an !== bn) return an ? -1 : 1
|
|
43
|
-
return compareUtf8Bytes(left.pre[i], right.pre[i])
|
|
44
|
-
}
|
|
45
|
-
return 0
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function readJson(path, label) {
|
|
49
|
-
try { return JSON.parse(readFileSync(path, 'utf8')) }
|
|
50
|
-
catch (error) { throw new Error(`${label} is missing or invalid: ${error.message}`) }
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function canonicalTarball(name, version) {
|
|
54
|
-
return `https://registry.npmjs.org/${name}/-/${name.split('/').at(-1)}-${version}.tgz`
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function inspectCandidateSource(root, { allowInstalledDependencies = false } = {}) {
|
|
58
|
-
const rootStat = lstatSync(root)
|
|
59
|
-
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) throw new Error('candidate root must be a real directory')
|
|
60
|
-
const digest = createHash('sha256')
|
|
61
|
-
const visit = (directory) => {
|
|
62
|
-
for (const entry of readdirSync(directory).sort()) {
|
|
63
|
-
const path = join(directory, entry)
|
|
64
|
-
const rel = relative(root, path).replaceAll('\\', '/')
|
|
65
|
-
const stat = lstatSync(path)
|
|
66
|
-
if (rel === '.git') {
|
|
67
|
-
if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error('candidate .git must be a real directory')
|
|
68
|
-
continue
|
|
69
|
-
}
|
|
70
|
-
if (entry === 'node_modules') {
|
|
71
|
-
if (!allowInstalledDependencies) throw new Error(`candidate preinstall dependency tree forbidden: ${rel}`)
|
|
72
|
-
if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error(`installed dependency root must be a real directory: ${rel}`)
|
|
73
|
-
if (rel !== 'node_modules' && !/^apps\/[A-Za-z0-9._-]+\/node_modules$/.test(rel)) {
|
|
74
|
-
throw new Error(`installed dependency tree is outside canonical npm roots: ${rel}`)
|
|
75
|
-
}
|
|
76
|
-
continue
|
|
77
|
-
}
|
|
78
|
-
if (stat.isSymbolicLink()) throw new Error(`candidate symlink forbidden: ${rel}`)
|
|
79
|
-
if (stat.isDirectory()) {
|
|
80
|
-
digest.update(`directory\0${rel}\0${stat.mode & 0o777}\0`)
|
|
81
|
-
visit(path)
|
|
82
|
-
} else if (stat.isFile()) {
|
|
83
|
-
digest.update(`file\0${rel}\0${stat.mode & 0o777}\0`)
|
|
84
|
-
digest.update(readFileSync(path))
|
|
85
|
-
digest.update('\0')
|
|
86
|
-
} else {
|
|
87
|
-
throw new Error(`candidate special filesystem entry forbidden: ${rel}`)
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
visit(root)
|
|
92
|
-
return digest.digest('hex')
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function packageSetDigest(packages) {
|
|
96
|
-
return createHash('sha256').update(JSON.stringify(packages)).digest('hex')
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function validateInstalledWorkspaceLinks(candidate, lock) {
|
|
100
|
-
const dependencyRoot = join(candidate, 'node_modules')
|
|
101
|
-
const expected = new Map(Object.entries(lock.packages)
|
|
102
|
-
.filter(([, entry]) => entry?.link === true)
|
|
103
|
-
.map(([path, entry]) => [path, entry.resolved]))
|
|
104
|
-
const observed = new Map()
|
|
105
|
-
const inspectLink = (absolute, rel) => {
|
|
106
|
-
const stat = lstatSync(absolute)
|
|
107
|
-
if (!stat.isSymbolicLink()) return
|
|
108
|
-
observed.set(rel, resolve(join(absolute, '..'), readlinkSync(absolute)))
|
|
109
|
-
}
|
|
110
|
-
for (const name of readdirSync(dependencyRoot).sort()) {
|
|
111
|
-
const absolute = join(dependencyRoot, name)
|
|
112
|
-
const stat = lstatSync(absolute)
|
|
113
|
-
const rel = `node_modules/${name}`
|
|
114
|
-
if (stat.isSymbolicLink()) inspectLink(absolute, rel)
|
|
115
|
-
else if (stat.isDirectory() && name.startsWith('@')) {
|
|
116
|
-
for (const child of readdirSync(absolute).sort()) inspectLink(join(absolute, child), `${rel}/${child}`)
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
for (const [path, target] of observed) {
|
|
120
|
-
if (!expected.has(path)) throw new Error(`installed tree contains an unexpected workspace symlink: ${path}`)
|
|
121
|
-
if (target !== resolve(candidate, expected.get(path))) throw new Error(`installed workspace symlink target differs from the exact lock: ${path}`)
|
|
122
|
-
}
|
|
123
|
-
for (const [path, target] of expected) {
|
|
124
|
-
const absolute = join(candidate, path)
|
|
125
|
-
if (!observed.has(path) || !lstatSync(absolute).isSymbolicLink() || observed.get(path) !== resolve(candidate, target)) {
|
|
126
|
-
throw new Error(`installed tree is missing the exact locked workspace symlink: ${path}`)
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export function createPreinstallEvidence(locked, candidateRoot) {
|
|
132
|
-
return {
|
|
133
|
-
schemaVersion: 1,
|
|
134
|
-
candidateRoot: resolve(candidateRoot),
|
|
135
|
-
sourceDigest: locked.sourceDigest,
|
|
136
|
-
designSystem: locked.designSystem,
|
|
137
|
-
lockEntries: locked.files,
|
|
138
|
-
packageSetDigest: packageSetDigest(locked.packages),
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function validatePreinstallEvidence(evidence, candidateRoot) {
|
|
143
|
-
const expectedKeys = ['candidateRoot', 'designSystem', 'lockEntries', 'packageSetDigest', 'schemaVersion', 'sourceDigest']
|
|
144
|
-
if (!evidence || typeof evidence !== 'object' || Array.isArray(evidence)
|
|
145
|
-
|| JSON.stringify(Object.keys(evidence).sort()) !== JSON.stringify(expectedKeys)) {
|
|
146
|
-
throw new Error('preinstall evidence has an open or incomplete shape')
|
|
147
|
-
}
|
|
148
|
-
if (evidence.schemaVersion !== 1 || evidence.candidateRoot !== resolve(candidateRoot)) throw new Error('preinstall evidence candidate/schema binding mismatch')
|
|
149
|
-
if (!/^[0-9a-f]{64}$/.test(evidence.sourceDigest || '') || !/^[0-9a-f]{64}$/.test(evidence.packageSetDigest || '')) {
|
|
150
|
-
throw new Error('preinstall evidence digest is invalid')
|
|
151
|
-
}
|
|
152
|
-
if (!Number.isSafeInteger(evidence.lockEntries) || evidence.lockEntries < 1) throw new Error('preinstall evidence lock cardinality is invalid')
|
|
153
|
-
parseVersion(evidence.designSystem)
|
|
154
|
-
return evidence
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function canonicalRegistryMetadataUrl(name, version) {
|
|
158
|
-
if (!PACKAGES.includes(name)) throw new Error(`registry package is outside the closed governance set: ${name}`)
|
|
159
|
-
parseVersion(version)
|
|
160
|
-
return `${REGISTRY_ORIGIN}/${name.replace('/', '%2F')}/${encodeURIComponent(version)}`
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export async function liveRegistry(name, version, {
|
|
164
|
-
fetchImpl = globalThis.fetch,
|
|
165
|
-
signal = AbortSignal.timeout(30_000),
|
|
166
|
-
} = {}) {
|
|
167
|
-
if (typeof fetchImpl !== 'function') throw new Error('native HTTPS registry transport is unavailable')
|
|
168
|
-
const expectedUrl = canonicalRegistryMetadataUrl(name, version)
|
|
169
|
-
const response = await fetchImpl(expectedUrl, {
|
|
170
|
-
method: 'GET',
|
|
171
|
-
redirect: 'error',
|
|
172
|
-
credentials: 'omit',
|
|
173
|
-
cache: 'no-store',
|
|
174
|
-
referrerPolicy: 'no-referrer',
|
|
175
|
-
headers: {
|
|
176
|
-
Accept: 'application/json',
|
|
177
|
-
'User-Agent': REGISTRY_USER_AGENT,
|
|
178
|
-
},
|
|
179
|
-
signal,
|
|
180
|
-
})
|
|
181
|
-
if (!response || response.status !== 200) {
|
|
182
|
-
throw new Error(`registry metadata response status is not exactly 200: ${String(response?.status)}`)
|
|
183
|
-
}
|
|
184
|
-
if (response.url !== expectedUrl) throw new Error('registry metadata response URL differs from the exact request')
|
|
185
|
-
|
|
186
|
-
const contentType = response.headers?.get?.('content-type') || ''
|
|
187
|
-
if (!/^application\/json(?:\s*;\s*charset=utf-8)?$/i.test(contentType)) {
|
|
188
|
-
throw new Error(`registry metadata content type is not exact JSON: ${contentType}`)
|
|
189
|
-
}
|
|
190
|
-
const declaredLength = response.headers?.get?.('content-length')
|
|
191
|
-
if (declaredLength !== null && declaredLength !== undefined) {
|
|
192
|
-
if (!/^(?:0|[1-9]\d*)$/.test(declaredLength)) throw new Error('registry metadata content length is invalid')
|
|
193
|
-
const length = Number(declaredLength)
|
|
194
|
-
if (!Number.isSafeInteger(length) || length > REGISTRY_RESPONSE_MAX_BYTES) {
|
|
195
|
-
throw new Error('registry metadata response exceeds the closed byte limit')
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
if (!response.body || typeof response.body[Symbol.asyncIterator] !== 'function') {
|
|
199
|
-
throw new Error('registry metadata response body is not a bounded byte stream')
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const chunks = []
|
|
203
|
-
let total = 0
|
|
204
|
-
for await (const chunk of response.body) {
|
|
205
|
-
if (!(chunk instanceof Uint8Array)) throw new Error('registry metadata response emitted a non-byte chunk')
|
|
206
|
-
total += chunk.byteLength
|
|
207
|
-
if (total > REGISTRY_RESPONSE_MAX_BYTES) throw new Error('registry metadata response exceeds the closed byte limit')
|
|
208
|
-
chunks.push(Buffer.from(chunk))
|
|
209
|
-
}
|
|
210
|
-
if (declaredLength !== null && declaredLength !== undefined && total !== Number(declaredLength)) {
|
|
211
|
-
throw new Error('registry metadata response length differs from the declared byte count')
|
|
212
|
-
}
|
|
213
|
-
if (total === 0) throw new Error('registry metadata response body is empty')
|
|
214
|
-
|
|
215
|
-
let raw
|
|
216
|
-
try {
|
|
217
|
-
raw = new TextDecoder('utf-8', { fatal: true }).decode(Buffer.concat(chunks, total))
|
|
218
|
-
} catch (error) {
|
|
219
|
-
throw new Error(`registry metadata response is not valid UTF-8: ${error.message}`)
|
|
220
|
-
}
|
|
221
|
-
let body
|
|
222
|
-
try {
|
|
223
|
-
body = JSON.parse(raw)
|
|
224
|
-
} catch (error) {
|
|
225
|
-
throw new Error(`registry metadata response is not valid JSON: ${error.message}`)
|
|
226
|
-
}
|
|
227
|
-
const expectedTarball = canonicalTarball(name, version)
|
|
228
|
-
if (!body || typeof body !== 'object' || Array.isArray(body)
|
|
229
|
-
|| body.name !== name || body.version !== version
|
|
230
|
-
|| !body.dist || typeof body.dist !== 'object' || Array.isArray(body.dist)
|
|
231
|
-
|| !/^sha512-[A-Za-z0-9+/]+={0,2}$/.test(body.dist.integrity || '')
|
|
232
|
-
|| body.dist.tarball !== expectedTarball) {
|
|
233
|
-
throw new Error('registry metadata identity or exact artifact binding is invalid')
|
|
234
|
-
}
|
|
235
|
-
return Object.freeze({
|
|
236
|
-
integrity: body.dist.integrity,
|
|
237
|
-
tarball: body.dist.tarball,
|
|
238
|
-
})
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
export async function verifyCandidateLock({
|
|
242
|
-
trustedRoot,
|
|
243
|
-
candidateRoot,
|
|
244
|
-
registryLookup = liveRegistry,
|
|
245
|
-
allowInstalledDependencies = false,
|
|
246
|
-
expectedSourceDigest = null,
|
|
247
|
-
}) {
|
|
248
|
-
const trusted = resolve(trustedRoot)
|
|
249
|
-
const candidate = resolve(candidateRoot)
|
|
250
|
-
assertNoRootNpmShrinkwrap(candidate, { errorPrefix: 'GOV-ANCHOR-PREFLIGHT-LOCK-001' })
|
|
251
|
-
const sourceDigest = inspectCandidateSource(candidate, { allowInstalledDependencies })
|
|
252
|
-
if (expectedSourceDigest && sourceDigest !== expectedSourceDigest) throw new Error('candidate source changed after the preinstall trust check')
|
|
253
|
-
|
|
254
|
-
const trustedBom = readJson(join(trusted, 'governance/lock.json'), 'trusted governance lock')
|
|
255
|
-
const candidateBom = readJson(join(candidate, 'governance/lock.json'), 'candidate governance lock')
|
|
256
|
-
validateUpgradeTrustPolicy(trustedBom.upgradeTrust)
|
|
257
|
-
validateUpgradeTrustPolicy(candidateBom.upgradeTrust)
|
|
258
|
-
if (JSON.stringify(candidateBom.upgradeTrust) !== JSON.stringify(trustedBom.upgradeTrust)) {
|
|
259
|
-
throw new Error('candidate upgrade trust policy differs from the protected base')
|
|
260
|
-
}
|
|
261
|
-
const pkg = readJson(join(candidate, 'package.json'), 'candidate package.json')
|
|
262
|
-
const lock = readJson(join(candidate, 'package-lock.json'), 'candidate package-lock.json')
|
|
263
|
-
if (JSON.stringify(pkg.workspaces) !== JSON.stringify(PRODUCT_WORKSPACES)) {
|
|
264
|
-
throw new Error(`candidate workspaces must equal the protected product roots: ${PRODUCT_WORKSPACES.join(', ')}`)
|
|
265
|
-
}
|
|
266
|
-
if (lock.lockfileVersion !== 3 || !lock.packages || typeof lock.packages !== 'object') throw new Error('candidate requires closed npm lockfileVersion 3')
|
|
267
|
-
|
|
268
|
-
const rootLock = lock.packages[''] || {}
|
|
269
|
-
if (JSON.stringify(rootLock.workspaces) !== JSON.stringify(PRODUCT_WORKSPACES)) {
|
|
270
|
-
throw new Error(`candidate root lock workspaces must equal the protected product roots: ${PRODUCT_WORKSPACES.join(', ')}`)
|
|
271
|
-
}
|
|
272
|
-
for (const name of PACKAGES) {
|
|
273
|
-
const declared = pkg.dependencies?.[name]
|
|
274
|
-
parseVersion(declared)
|
|
275
|
-
const key = name === PACKAGES[0] ? 'designSystem' : 'storybookConfig'
|
|
276
|
-
const trustedVersion = trustedBom.release?.[key]
|
|
277
|
-
if (compareVersion(declared, trustedVersion) < 0) throw new Error(`${name} downgrade ${declared} < trusted base ${trustedVersion}`)
|
|
278
|
-
if (candidateBom.release?.[key] !== declared) throw new Error(`${name} does not match candidate governance lock`)
|
|
279
|
-
if (rootLock.dependencies?.[name] !== declared) throw new Error(`${name} root lock declaration is not exact`)
|
|
280
|
-
|
|
281
|
-
const entry = lock.packages[`node_modules/${name}`]
|
|
282
|
-
const expectedTarball = canonicalTarball(name, declared)
|
|
283
|
-
if (entry?.version !== declared || entry?.resolved !== expectedTarball || !/^sha512-[A-Za-z0-9+/]+={0,2}$/.test(entry?.integrity || '')) {
|
|
284
|
-
throw new Error(`${name} lock entry is not the canonical exact npm artifact`)
|
|
285
|
-
}
|
|
286
|
-
const registry = await registryLookup(name, declared)
|
|
287
|
-
if (registry.tarball !== expectedTarball || registry.integrity !== entry.integrity) throw new Error(`${name} lock does not match live npm registry metadata`)
|
|
288
|
-
}
|
|
289
|
-
if (pkg.dependencies[PACKAGES[0]] !== pkg.dependencies[PACKAGES[1]]) throw new Error('DS and Storybook governance releases must move in lockstep')
|
|
290
|
-
|
|
291
|
-
for (const [path, entry] of Object.entries(lock.packages)) {
|
|
292
|
-
if (!entry || path === '') continue
|
|
293
|
-
if (entry.link === true) {
|
|
294
|
-
if (!PRODUCT_WORKSPACE_LINK.test(path) || !PRODUCT_WORKSPACE_PATH.test(entry.resolved || '')) throw new Error(`unsafe or non-product workspace link in lock: ${path}`)
|
|
295
|
-
const workspace = readJson(join(candidate, entry.resolved, 'package.json'), `workspace manifest for ${entry.resolved}`)
|
|
296
|
-
if (workspace.private !== true || workspace.name !== path.slice('node_modules/'.length)) throw new Error(`workspace link identity is not private and exact: ${path}`)
|
|
297
|
-
continue
|
|
298
|
-
}
|
|
299
|
-
if (entry.resolved && !String(entry.resolved).startsWith('https://registry.npmjs.org/')) throw new Error(`non-canonical registry source in lock: ${path}`)
|
|
300
|
-
if (entry.resolved && !/^sha512-[A-Za-z0-9+/]+={0,2}$/.test(entry.integrity || '')) throw new Error(`missing sha512 integrity in lock: ${path}`)
|
|
301
|
-
}
|
|
302
|
-
if (allowInstalledDependencies) validateInstalledWorkspaceLinks(candidate, lock)
|
|
303
|
-
|
|
304
|
-
return {
|
|
305
|
-
designSystem: pkg.dependencies[PACKAGES[0]],
|
|
306
|
-
files: Object.keys(lock.packages).length,
|
|
307
|
-
sourceDigest,
|
|
308
|
-
policy: trustedBom.upgradeTrust,
|
|
309
|
-
packages: PACKAGES.map((name) => ({
|
|
310
|
-
name,
|
|
311
|
-
version: pkg.dependencies[name],
|
|
312
|
-
integrity: lock.packages[`node_modules/${name}`].integrity,
|
|
313
|
-
})),
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
export async function verifyCandidate({
|
|
318
|
-
trustedRoot,
|
|
319
|
-
candidateRoot,
|
|
320
|
-
registryLookup = liveRegistry,
|
|
321
|
-
auditReport,
|
|
322
|
-
releaseLookup,
|
|
323
|
-
preinstallEvidence = null,
|
|
324
|
-
}) {
|
|
325
|
-
const evidence = preinstallEvidence ? validatePreinstallEvidence(preinstallEvidence, candidateRoot) : null
|
|
326
|
-
const locked = await verifyCandidateLock({
|
|
327
|
-
trustedRoot,
|
|
328
|
-
candidateRoot,
|
|
329
|
-
registryLookup,
|
|
330
|
-
allowInstalledDependencies: Boolean(evidence),
|
|
331
|
-
expectedSourceDigest: evidence?.sourceDigest || null,
|
|
332
|
-
})
|
|
333
|
-
if (evidence && (
|
|
334
|
-
locked.designSystem !== evidence.designSystem
|
|
335
|
-
|| locked.files !== evidence.lockEntries
|
|
336
|
-
|| packageSetDigest(locked.packages) !== evidence.packageSetDigest
|
|
337
|
-
)) throw new Error('candidate lock identity changed after the preinstall trust check')
|
|
338
|
-
if (!auditReport) throw new Error('verified npm audit-signatures JSON with provenance bundles is required')
|
|
339
|
-
const provenance = await verifyUpgradeProvenance({
|
|
340
|
-
auditReport,
|
|
341
|
-
packages: locked.packages,
|
|
342
|
-
version: locked.designSystem,
|
|
343
|
-
policy: locked.policy,
|
|
344
|
-
...(releaseLookup ? { releaseLookup } : {}),
|
|
345
|
-
})
|
|
346
|
-
return { designSystem: locked.designSystem, files: locked.files, provenance }
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
if (fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
350
|
-
const rawArgs = process.argv.slice(2)
|
|
351
|
-
const valueOf = (name) => {
|
|
352
|
-
const index = rawArgs.indexOf(name)
|
|
353
|
-
return index >= 0 ? rawArgs[index + 1] : undefined
|
|
354
|
-
}
|
|
355
|
-
const args = {
|
|
356
|
-
trusted: valueOf('--trusted'),
|
|
357
|
-
candidate: valueOf('--candidate'),
|
|
358
|
-
verifiedAttestations: valueOf('--verified-attestations'),
|
|
359
|
-
evidenceOut: valueOf('--evidence-out'),
|
|
360
|
-
preinstallEvidence: valueOf('--preinstall-evidence'),
|
|
361
|
-
lockOnly: rawArgs.includes('--lock-only'),
|
|
362
|
-
}
|
|
363
|
-
if (!args.trusted || !args.candidate) {
|
|
364
|
-
console.error('usage: governance-anchor-preflight.mjs --trusted <base> --candidate <head> [--lock-only --evidence-out <file> | --verified-attestations <npm-audit.json> --preinstall-evidence <file>]')
|
|
365
|
-
process.exit(2)
|
|
366
|
-
}
|
|
367
|
-
try {
|
|
368
|
-
if (args.lockOnly === Boolean(args.verifiedAttestations)
|
|
369
|
-
|| args.lockOnly !== Boolean(args.evidenceOut)
|
|
370
|
-
|| Boolean(args.verifiedAttestations) !== Boolean(args.preinstallEvidence)) {
|
|
371
|
-
throw new Error('select lock-only with evidence-out, or verified-attestations with preinstall-evidence')
|
|
372
|
-
}
|
|
373
|
-
let result
|
|
374
|
-
if (args.lockOnly) {
|
|
375
|
-
result = await verifyCandidateLock({ trustedRoot: args.trusted, candidateRoot: args.candidate })
|
|
376
|
-
writeFileSync(resolve(args.evidenceOut), `${JSON.stringify(createPreinstallEvidence(result, args.candidate), null, 2)}\n`, { flag: 'wx', mode: 0o600 })
|
|
377
|
-
} else {
|
|
378
|
-
result = await verifyCandidate({
|
|
379
|
-
trustedRoot: args.trusted,
|
|
380
|
-
candidateRoot: args.candidate,
|
|
381
|
-
auditReport: readJson(resolve(args.verifiedAttestations), 'verified npm attestation report'),
|
|
382
|
-
preinstallEvidence: readJson(resolve(args.preinstallEvidence), 'preinstall trust evidence'),
|
|
383
|
-
})
|
|
384
|
-
}
|
|
385
|
-
const identity = result.provenance
|
|
386
|
-
? `; ${result.provenance.repository}@${result.provenance.gitCommit} ${result.provenance.releaseAssetDigest}`
|
|
387
|
-
: '; lock-only'
|
|
388
|
-
console.log(`✅ base-trusted candidate preflight PASS (${result.designSystem}; ${result.files} lock entries${identity})`)
|
|
389
|
-
} catch (error) {
|
|
390
|
-
console.error(`❌ GOV-ANCHOR-PREFLIGHT: ${error.message}`)
|
|
391
|
-
process.exit(1)
|
|
392
|
-
}
|
|
393
|
-
}
|
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
name: Governance anchor
|
|
2
|
-
|
|
3
|
-
# This workflow is loaded from protected base. Candidate code runs only in the
|
|
4
|
-
# credential-free verify job. A separate fresh runner, restricted to the
|
|
5
|
-
# protected-branches-only governance-check-verdict environment, publishes the
|
|
6
|
-
# verdict with the dedicated check-only App. Rulesets pin that App integration
|
|
7
|
-
# ID, never the spoofable github-actions job context.
|
|
8
|
-
on:
|
|
9
|
-
pull_request_target:
|
|
10
|
-
types: [opened, synchronize, reopened, ready_for_review]
|
|
11
|
-
repository_dispatch:
|
|
12
|
-
# The dedicated Writer App may trigger ordinary candidate workflows, but those
|
|
13
|
-
# runs are never the required trust anchor. This explicit event reloads only the
|
|
14
|
-
# protected-default workflow and binds the automatic Check App validation path.
|
|
15
|
-
types: [governance-upgrade-candidate-validation]
|
|
16
|
-
|
|
17
|
-
permissions:
|
|
18
|
-
contents: read
|
|
19
|
-
|
|
20
|
-
jobs:
|
|
21
|
-
verify-candidate:
|
|
22
|
-
name: Base-trusted candidate verification
|
|
23
|
-
runs-on: ubuntu-24.04
|
|
24
|
-
timeout-minutes: 25
|
|
25
|
-
outputs:
|
|
26
|
-
base_sha: ${{ steps.request.outputs.base_sha }}
|
|
27
|
-
candidate_repo: ${{ steps.request.outputs.candidate_repo }}
|
|
28
|
-
head_sha: ${{ steps.request.outputs.head_sha }}
|
|
29
|
-
pull_number: ${{ steps.request.outputs.pull_number }}
|
|
30
|
-
steps:
|
|
31
|
-
- name: Resolve live PR and bind explicit dispatch to protected main
|
|
32
|
-
id: request
|
|
33
|
-
env:
|
|
34
|
-
GH_TOKEN: ${{ github.token }}
|
|
35
|
-
EVENT_NAME: ${{ github.event_name }}
|
|
36
|
-
EVENT_SOURCE: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.source || 'pull-request-target-v1' }}
|
|
37
|
-
EVENT_PR_NUMBER: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.pull_number || github.event.pull_request.number }}
|
|
38
|
-
EVENT_HEAD_SHA: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.head_sha || github.event.pull_request.head.sha }}
|
|
39
|
-
EVENT_BASE_SHA: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.base_sha || github.event.pull_request.base.sha }}
|
|
40
|
-
run: |
|
|
41
|
-
test -n "$GH_TOKEN"
|
|
42
|
-
[[ "$EVENT_PR_NUMBER" =~ ^[1-9][0-9]*$ ]]
|
|
43
|
-
[[ "$EVENT_HEAD_SHA" =~ ^[a-f0-9]{40}$ ]]
|
|
44
|
-
[[ "$EVENT_BASE_SHA" =~ ^[a-f0-9]{40}$ ]]
|
|
45
|
-
if [ "$EVENT_NAME" = repository_dispatch ]; then
|
|
46
|
-
test "$EVENT_SOURCE" = governance-upgrade-writer-v1
|
|
47
|
-
jq -e '(.client_payload | keys | sort) == ["base_sha","head_sha","pull_number","source"]' "$GITHUB_EVENT_PATH" >/dev/null
|
|
48
|
-
fi
|
|
49
|
-
|
|
50
|
-
PR_JSON=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$EVENT_PR_NUMBER")
|
|
51
|
-
test "$(jq -r '.state' <<<"$PR_JSON")" = open
|
|
52
|
-
test "$(jq -r '.base.ref' <<<"$PR_JSON")" = main
|
|
53
|
-
test "$(jq -r '.base.sha' <<<"$PR_JSON")" = "$EVENT_BASE_SHA"
|
|
54
|
-
test "$(jq -r '.head.sha' <<<"$PR_JSON")" = "$EVENT_HEAD_SHA"
|
|
55
|
-
CANDIDATE_REPO=$(jq -r '.head.repo.full_name' <<<"$PR_JSON")
|
|
56
|
-
HEAD_REF=$(jq -r '.head.ref' <<<"$PR_JSON")
|
|
57
|
-
[[ "$CANDIDATE_REPO" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]
|
|
58
|
-
[[ "$HEAD_REF" =~ ^[A-Za-z0-9._/-]+$ ]]
|
|
59
|
-
if [ "$EVENT_NAME" = repository_dispatch ]; then
|
|
60
|
-
test "$CANDIDATE_REPO" = "$GITHUB_REPOSITORY"
|
|
61
|
-
[[ "$HEAD_REF" =~ ^governance/upgrade- ]]
|
|
62
|
-
fi
|
|
63
|
-
LIVE_MAIN_SHA=$(gh api "repos/$GITHUB_REPOSITORY/git/ref/heads/main" --jq '.object.sha')
|
|
64
|
-
test "$LIVE_MAIN_SHA" = "$EVENT_BASE_SHA" || {
|
|
65
|
-
echo 'GOV-ANCHOR-SOURCE-001: protected main moved before candidate validation'
|
|
66
|
-
exit 1
|
|
67
|
-
}
|
|
68
|
-
echo "base_sha=$EVENT_BASE_SHA" >> "$GITHUB_OUTPUT"
|
|
69
|
-
echo "candidate_repo=$CANDIDATE_REPO" >> "$GITHUB_OUTPUT"
|
|
70
|
-
echo "head_sha=$EVENT_HEAD_SHA" >> "$GITHUB_OUTPUT"
|
|
71
|
-
echo "pull_number=$EVENT_PR_NUMBER" >> "$GITHUB_OUTPUT"
|
|
72
|
-
|
|
73
|
-
- name: Checkout protected base verifier
|
|
74
|
-
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
75
|
-
with:
|
|
76
|
-
ref: ${{ steps.request.outputs.base_sha }}
|
|
77
|
-
path: trusted
|
|
78
|
-
persist-credentials: false
|
|
79
|
-
|
|
80
|
-
- name: Checkout candidate as untrusted data
|
|
81
|
-
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
82
|
-
with:
|
|
83
|
-
repository: ${{ steps.request.outputs.candidate_repo }}
|
|
84
|
-
ref: ${{ steps.request.outputs.head_sha }}
|
|
85
|
-
path: candidate
|
|
86
|
-
persist-credentials: false
|
|
87
|
-
|
|
88
|
-
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
|
89
|
-
with:
|
|
90
|
-
node-version: '24.14.0'
|
|
91
|
-
|
|
92
|
-
- name: Install protected-base verifier dependencies without lifecycle execution
|
|
93
|
-
working-directory: trusted
|
|
94
|
-
env:
|
|
95
|
-
NPM_CONFIG_USERCONFIG: ${{ runner.temp }}/empty-user.npmrc
|
|
96
|
-
NPM_CONFIG_GLOBALCONFIG: ${{ runner.temp }}/empty-global.npmrc
|
|
97
|
-
run: |
|
|
98
|
-
npm run setup:governance
|
|
99
|
-
|
|
100
|
-
- name: Stage protected product-check tools and exact Playwright runtime before candidate execution
|
|
101
|
-
run: |
|
|
102
|
-
node trusted/scripts/consumer-source-harness.mjs \
|
|
103
|
-
--stage-trusted-product-checks trusted/.governance-tools \
|
|
104
|
-
--trusted trusted
|
|
105
|
-
node trusted/node_modules/playwright/cli.js install --with-deps chromium
|
|
106
|
-
|
|
107
|
-
- name: Reject downgrade, alternate registry, lock and provenance substitution
|
|
108
|
-
run: >-
|
|
109
|
-
node trusted/scripts/governance-anchor-preflight.mjs
|
|
110
|
-
--trusted trusted --candidate candidate --lock-only
|
|
111
|
-
--evidence-out "$RUNNER_TEMP/candidate-preinstall-evidence.json"
|
|
112
|
-
|
|
113
|
-
- name: Install and audit candidate dependencies without executing installed code
|
|
114
|
-
working-directory: candidate
|
|
115
|
-
env:
|
|
116
|
-
NPM_CONFIG_USERCONFIG: ${{ runner.temp }}/empty-user.npmrc
|
|
117
|
-
NPM_CONFIG_GLOBALCONFIG: ${{ runner.temp }}/empty-global.npmrc
|
|
118
|
-
run: >-
|
|
119
|
-
node ../trusted/scripts/setup-governance.mjs
|
|
120
|
-
--dependencies-only --root .
|
|
121
|
-
|
|
122
|
-
- name: Verify every registry signature
|
|
123
|
-
working-directory: candidate
|
|
124
|
-
env:
|
|
125
|
-
NPM_CONFIG_USERCONFIG: ${{ runner.temp }}/empty-user.npmrc
|
|
126
|
-
NPM_CONFIG_GLOBALCONFIG: ${{ runner.temp }}/empty-global.npmrc
|
|
127
|
-
run: |
|
|
128
|
-
EXPECTED_NPM=$(node ../trusted/scripts/workflow-static-validation.mjs \
|
|
129
|
-
print-exact-npm-version --manifest ../trusted/package.json)
|
|
130
|
-
ACTUAL_NPM=$(node ../trusted/node_modules/npm/bin/npm-cli.js --version)
|
|
131
|
-
test "$ACTUAL_NPM" = "$EXPECTED_NPM"
|
|
132
|
-
node ../trusted/node_modules/npm/bin/npm-cli.js audit signatures \
|
|
133
|
-
--json --include-attestations --registry=https://registry.npmjs.org/ \
|
|
134
|
-
> "$RUNNER_TEMP/verified-attestations.json"
|
|
135
|
-
|
|
136
|
-
- name: Bind verified provenance to immutable GitHub Release BOM
|
|
137
|
-
run: >-
|
|
138
|
-
node trusted/scripts/governance-anchor-preflight.mjs
|
|
139
|
-
--trusted trusted --candidate candidate
|
|
140
|
-
--verified-attestations "$RUNNER_TEMP/verified-attestations.json"
|
|
141
|
-
--preinstall-evidence "$RUNNER_TEMP/candidate-preinstall-evidence.json"
|
|
142
|
-
|
|
143
|
-
- name: Execute only the authenticated immutable checker
|
|
144
|
-
working-directory: candidate
|
|
145
|
-
run: >-
|
|
146
|
-
node ../trusted/scripts/setup-governance.mjs
|
|
147
|
-
--installed-check-only --root .
|
|
148
|
-
|
|
149
|
-
- name: Base-owned exhaustive workspace type/build harness under a candidate-only identity
|
|
150
|
-
run: |
|
|
151
|
-
sudo useradd --system --user-group --no-create-home --shell /usr/sbin/nologin governance-candidate
|
|
152
|
-
sudo install -d -o governance-candidate -g governance-candidate -m 0700 "$RUNNER_TEMP/governance-candidate-home"
|
|
153
|
-
sudo chown -R governance-candidate:governance-candidate candidate
|
|
154
|
-
NODE_BIN=$(command -v node)
|
|
155
|
-
test -n "$NODE_BIN"
|
|
156
|
-
NODE_DIR=$(dirname "$NODE_BIN")
|
|
157
|
-
sudo -u governance-candidate env -i \
|
|
158
|
-
HOME="$RUNNER_TEMP/governance-candidate-home" \
|
|
159
|
-
PATH="$NODE_DIR:/usr/bin:/bin" \
|
|
160
|
-
CI=true \
|
|
161
|
-
"$NODE_BIN" trusted/.governance-tools/scripts/consumer-source-harness.mjs --repo candidate
|
|
162
|
-
set +e
|
|
163
|
-
sudo pkill -KILL -u governance-candidate
|
|
164
|
-
PKILL_STATUS=$?
|
|
165
|
-
set -e
|
|
166
|
-
test "$PKILL_STATUS" = 0 || test "$PKILL_STATUS" = 1
|
|
167
|
-
sudo chmod -R a-w candidate
|
|
168
|
-
|
|
169
|
-
- name: Reverify protected tools then treat the frozen candidate only as product-check input
|
|
170
|
-
run: |
|
|
171
|
-
node trusted/scripts/consumer-source-harness.mjs \
|
|
172
|
-
--verify-trusted-product-checks trusted/.governance-tools \
|
|
173
|
-
--trusted trusted
|
|
174
|
-
node trusted/.governance-tools/scripts/lint-ds-internal-imports.mjs --repo candidate
|
|
175
|
-
node trusted/.governance-tools/scripts/audit-consumer-a11y.mjs --repo candidate
|
|
176
|
-
|
|
177
|
-
publish-app-verdict:
|
|
178
|
-
name: Publish Governance App verdict
|
|
179
|
-
if: ${{ always() && needs.verify-candidate.outputs.head_sha != '' }}
|
|
180
|
-
needs: verify-candidate
|
|
181
|
-
runs-on: ubuntu-24.04
|
|
182
|
-
timeout-minutes: 5
|
|
183
|
-
environment:
|
|
184
|
-
name: governance-check-verdict
|
|
185
|
-
steps:
|
|
186
|
-
- name: Mint check-only Governance App token on a fresh runner
|
|
187
|
-
id: app-token
|
|
188
|
-
uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2
|
|
189
|
-
with:
|
|
190
|
-
app-id: ${{ secrets.GOVERNANCE_CHECK_APP_ID }}
|
|
191
|
-
private-key: ${{ secrets.GOVERNANCE_CHECK_APP_PRIVATE_KEY }}
|
|
192
|
-
owner: ${{ github.repository_owner }}
|
|
193
|
-
repositories: ${{ github.event.repository.name }}
|
|
194
|
-
permission-checks: write
|
|
195
|
-
|
|
196
|
-
- name: Publish non-spoofable aggregate check
|
|
197
|
-
env:
|
|
198
|
-
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
199
|
-
HEAD_SHA: ${{ needs.verify-candidate.outputs.head_sha }}
|
|
200
|
-
WORKFLOW_REF: ${{ github.workflow_ref }}
|
|
201
|
-
WORKFLOW_SHA: ${{ github.workflow_sha }}
|
|
202
|
-
RUN_ID: ${{ github.run_id }}
|
|
203
|
-
RUN_ATTEMPT: ${{ github.run_attempt }}
|
|
204
|
-
DETAILS_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
205
|
-
VERIFY_RESULT: ${{ needs.verify-candidate.result }}
|
|
206
|
-
run: |
|
|
207
|
-
if [ "$VERIFY_RESULT" = success ]; then
|
|
208
|
-
CONCLUSION=success
|
|
209
|
-
SUMMARY='Base-trusted preflight, signed immutable governance, typecheck, build and accessibility audit passed.'
|
|
210
|
-
else
|
|
211
|
-
CONCLUSION=failure
|
|
212
|
-
SUMMARY="Base-trusted verification ended with: $VERIFY_RESULT"
|
|
213
|
-
fi
|
|
214
|
-
jq -n \
|
|
215
|
-
--arg name 'Immutable consumer snapshot' \
|
|
216
|
-
--arg head_sha "$HEAD_SHA" \
|
|
217
|
-
--arg external_id "governance-check-v1|$WORKFLOW_REF|$WORKFLOW_SHA|$HEAD_SHA|$RUN_ID|$RUN_ATTEMPT" \
|
|
218
|
-
--arg details_url "$DETAILS_URL" \
|
|
219
|
-
--arg conclusion "$CONCLUSION" \
|
|
220
|
-
--arg summary "$SUMMARY" \
|
|
221
|
-
'{name:$name,head_sha:$head_sha,external_id:$external_id,details_url:$details_url,status:"completed",conclusion:$conclusion,output:{title:$name,summary:$summary}}' \
|
|
222
|
-
| gh api --method POST "repos/$GITHUB_REPOSITORY/check-runs" --input -
|