@qijenchen/design-system 0.1.0-beta.104 → 0.1.0-beta.105
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/ds-canonical/fork/codex/agents/skills/code-quality-audit/SKILL.md +2 -2
- package/ds-canonical/fork/codex/agents/skills/product-ui-audit/references/report-template.md +1 -1
- package/ds-canonical/fork/codex/agents/skills/scan-similar-bugs/SKILL.md +1 -1
- package/ds-canonical/fork/codex/agents/skills/visual-audit/SKILL.md +1 -1
- package/ds-canonical/fork/consumer/governance-check.mjs +20 -4
- package/ds-canonical/fork/consumer/lock.json +12 -12
- package/ds-canonical/fork/consumer/managed-files/794bb911ba8ebef58ab0d44a55b9968a19d2c62bcfb310b1ee3a84166f087f21.blob +69 -42
- package/ds-canonical/fork/consumer/managed-files/884721550b56308812b20917adac7a12f36fdb4ecda6b02460a7f5a219c0c966.blob +10 -13
- package/ds-canonical/fork/consumer/managed-files/bc44cabad181f766611dab6a8f52d81100afadd50ac59799015e8ba7bf46d9eb.blob +50 -27
- package/ds-canonical/fork/governance.lock +34 -26
- package/ds-canonical/fork/hooks/check_consumer_app_invariants.sh +211 -1
- package/ds-canonical/fork/hooks/check_escape_marker_abuse.sh +17 -15
- package/ds-canonical/fork/hooks/check_layout_space_magic_numbers.sh +22 -1
- package/ds-canonical/fork/hooks/lib/_micro_geometry.sh +291 -0
- package/ds-canonical/fork/manifest.json +10 -10
- package/ds-canonical/fork/providers/claude/skills/code-quality-audit/SKILL.md +2 -2
- package/ds-canonical/fork/providers/claude/skills/product-ui-audit/references/report-template.md +1 -1
- package/ds-canonical/fork/providers/claude/skills/scan-similar-bugs/SKILL.md +1 -1
- package/ds-canonical/fork/providers/claude/skills/visual-audit/SKILL.md +1 -1
- package/ds-canonical/fork/providers/codex/skills/code-quality-audit/SKILL.md +2 -2
- package/ds-canonical/fork/providers/codex/skills/product-ui-audit/references/report-template.md +1 -1
- package/ds-canonical/fork/providers/codex/skills/scan-similar-bugs/SKILL.md +1 -1
- package/ds-canonical/fork/providers/codex/skills/visual-audit/SKILL.md +1 -1
- package/ds-canonical/fork/references/utility-registry.json +50 -1
- package/ds-canonical/fork/references/utility-registry.schema.json +37 -1
- package/ds-canonical/fork/skills/code-quality-audit/SKILL.md +2 -2
- package/ds-canonical/fork/skills/product-ui-audit/references/report-template.md +1 -1
- package/ds-canonical/fork/skills/scan-similar-bugs/SKILL.md +1 -1
- package/ds-canonical/fork/skills/visual-audit/SKILL.md +1 -1
- package/ds-canonical/hooks/check_consumer_app_invariants.sh +211 -1
- package/ds-canonical/hooks/check_escape_marker_abuse.sh +17 -15
- package/ds-canonical/hooks/check_layout_space_magic_numbers.sh +22 -1
- package/ds-canonical/hooks/lib/_micro_geometry.sh +291 -0
- package/ds-canonical/hooks/tests/test_check_consumer_ds_primitive_misuse.sh +80 -0
- package/ds-canonical/hooks/tests/test_check_escape_marker_abuse.sh +52 -0
- package/ds-canonical/hooks/tests/test_check_layout_space_magic_numbers.sh +58 -0
- package/ds-canonical/references/build-ui-canonicals.md +13 -0
- package/ds-canonical/references/ssot-index.md +1 -1
- package/ds-canonical/rules/meta-patterns.md +1 -1
- package/ds-canonical/skills/code-quality-audit/SKILL.md +2 -2
- package/ds-canonical/skills/codify-corrections/SKILL.md +4 -4
- package/ds-canonical/skills/codify-principle/SKILL.md +2 -2
- package/ds-canonical/skills/design-system-audit/SKILL.md +4 -4
- package/ds-canonical/skills/design-system-audit/references/audit-prompts.md +4 -2
- package/ds-canonical/skills/design-system-audit/references/principle-audit-protocol.md +1 -1
- package/ds-canonical/skills/product-ui-audit/references/report-template.md +1 -1
- package/ds-canonical/skills/scan-similar-bugs/SKILL.md +1 -1
- package/ds-canonical/skills/story-writing/SKILL.md +1 -1
- package/ds-canonical/skills/visual-audit/SKILL.md +1 -1
- package/ds-canonical/templates/consumer-governance.md +10 -13
- package/package.json +1 -1
- package/src/components/BulkActionBar/bulk-action-bar.principles.stories.tsx +3 -3
- package/src/components/Button/button.principles.stories.tsx +2 -2
- package/src/components/Chart/chart.principles.stories.tsx +2 -2
- package/src/tokens/layoutSpace/layoutSpace.spec.md +1 -1
- package/src/tokens/utility-registry.json +50 -1
- package/src/tokens/utility-registry.schema.json +37 -1
|
@@ -45,8 +45,8 @@ Scope:**tsx / ts code hygiene**,跟 design canonical 正交。
|
|
|
45
45
|
與 long-function remediation 都是工程決策,依 Standing Authorization 自主完成並驗證。
|
|
46
46
|
- 只有修法會改變 public component semantics、產品 workflow、interaction 或其他產品/
|
|
47
47
|
UI/UX SSOT,且證據收斂後仍有真實取捨時,才停下取得 exact target-bound decision。
|
|
48
|
-
- 無法分類時 fail closed 並補 source/evidence
|
|
49
|
-
approval 代替工程判斷。
|
|
48
|
+
- 無法分類時 fail closed 並補 source/evidence;task/deliverable 明確要求時才加
|
|
49
|
+
independent review,不得用泛用 user approval 代替工程判斷。
|
|
50
50
|
|
|
51
51
|
## 禁止
|
|
52
52
|
|
package/ds-canonical/fork/codex/agents/skills/product-ui-audit/references/report-template.md
CHANGED
|
@@ -47,7 +47,7 @@ scope / 設計決策 / 業務判斷:
|
|
|
47
47
|
|
|
48
48
|
**處理**:
|
|
49
49
|
- **P2E**:engineering/governance/refactor/a11y/performance 或既有 SSOT 唯一落地,依
|
|
50
|
-
evidence +
|
|
50
|
+
evidence + hard gates 自主修;task/deliverable 明確要求時才加 independent review。
|
|
51
51
|
- **P2H**:只有會改變產品/UI/UX SSOT 且證據收斂後仍存在真實取捨,才由 user
|
|
52
52
|
逐項做 exact target-bound decision。
|
|
53
53
|
|
|
@@ -108,7 +108,7 @@ Phase 1 found N candidates of same anti-pattern:
|
|
|
108
108
|
|
|
109
109
|
Execution:
|
|
110
110
|
- deterministic fix → Auto-fix all confirmed N
|
|
111
|
-
- engineering judgment → 依 owning spec + highest certified capability
|
|
111
|
+
- engineering judgment → 依 owning spec + highest certified capability 逐檔收斂;task/deliverable 明確要求時才加 independent review
|
|
112
112
|
- 不屬 frozen scope → Backlog with evidence;不得用 defer 逃避 scope 內修復
|
|
113
113
|
- 會改產品/UI/UX SSOT 且仍有真取捨 → batch-at-end human-only decision
|
|
114
114
|
```
|
|
@@ -217,7 +217,7 @@ FAIL 項摘要:
|
|
|
217
217
|
|
|
218
218
|
後續路由:
|
|
219
219
|
1. FAIL 可由既有 canonical 決定 → P2E,由 primary context 依 Standing Authorization remediation + verify
|
|
220
|
-
2. 純工程/治理原因尚未收斂 → P2E,依 Engineering Decision Policy、tests
|
|
220
|
+
2. 純工程/治理原因尚未收斂 → P2E,依 Engineering Decision Policy、tests 與 hard gates 收斂;task/deliverable 明確要求時才加 independent review,optional review 缺席不得阻擋一般工程;無法收斂則 fail closed 回報 technical blocker
|
|
221
221
|
3. 必須改變產品/UI/UX SSOT 且仍有真實選擇或取捨 → P2H,batch-at-end 請 user 拍板
|
|
222
222
|
4. 證據不足 → 補足 screenshot/量測,不以猜測結案
|
|
223
223
|
|
|
@@ -259,12 +259,22 @@ const readJson = (path, ruleId) => {
|
|
|
259
259
|
try { return JSON.parse(readFileSync(path, 'utf8')) }
|
|
260
260
|
catch (error) { add(ruleId, 'BLOCKER', `${relative(repo, path)} invalid JSON`, error.message); return null }
|
|
261
261
|
}
|
|
262
|
-
const treeInventory = (root) => {
|
|
262
|
+
const treeInventory = (root, { frozenCanonicalSnapshot = false } = {}) => {
|
|
263
263
|
if (!existsSync(root)) return null
|
|
264
264
|
const rows = []
|
|
265
265
|
const visit = (absolute, rel = '') => {
|
|
266
266
|
const stat = lstatSync(absolute)
|
|
267
|
-
const
|
|
267
|
+
const observedMode = stat.mode & 0o777
|
|
268
|
+
let projectedMode = observedMode
|
|
269
|
+
if (frozenCanonicalSnapshot) {
|
|
270
|
+
if (stat.isDirectory() && observedMode === 0o500) projectedMode = 0o755
|
|
271
|
+
else if (stat.isFile() && observedMode === 0o400) projectedMode = 0o644
|
|
272
|
+
else if (stat.isFile() && observedMode === 0o500) projectedMode = 0o755
|
|
273
|
+
else projectedMode = -1
|
|
274
|
+
}
|
|
275
|
+
const mode = projectedMode < 0
|
|
276
|
+
? `invalid-frozen-${observedMode.toString(8).padStart(4, '0')}`
|
|
277
|
+
: projectedMode.toString(8).padStart(4, '0')
|
|
268
278
|
if (stat.isSymbolicLink()) { rows.push(`link:${rel}:${mode}:${readlinkSync(absolute)}`); return }
|
|
269
279
|
if (stat.isDirectory()) {
|
|
270
280
|
rows.push(`dir:${rel || '.'}:${mode}`)
|
|
@@ -282,8 +292,14 @@ const assertTreeEqual = (ruleId, label, actual, canonical) => {
|
|
|
282
292
|
add(ruleId, 'BLOCKER', `${label} contains an unsafe symlink path component`)
|
|
283
293
|
return false
|
|
284
294
|
}
|
|
285
|
-
const
|
|
286
|
-
|
|
295
|
+
const isFrozenCanonicalPath = path => entrypointIsPrivateSnapshot
|
|
296
|
+
&& (path === forkRoot || path.startsWith(`${forkRoot}${sep}`))
|
|
297
|
+
const actualRows = treeInventory(actual, {
|
|
298
|
+
frozenCanonicalSnapshot: isFrozenCanonicalPath(actual),
|
|
299
|
+
})
|
|
300
|
+
const canonicalRows = treeInventory(canonical, {
|
|
301
|
+
frozenCanonicalSnapshot: isFrozenCanonicalPath(canonical),
|
|
302
|
+
})
|
|
287
303
|
if (!actualRows || !canonicalRows || JSON.stringify(actualRows) !== JSON.stringify(canonicalRows)) {
|
|
288
304
|
add(ruleId, 'BLOCKER', `${label} is not a recursive path/type/mode/hash match of the installed canonical projection`)
|
|
289
305
|
return false
|
|
@@ -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.105",
|
|
7
|
+
"storybookConfig": "0.1.0-beta.105"
|
|
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": "0d2489d8bb989a6cd7c039f2c7262deb6fcd0be26e67a21ede951003da953455",
|
|
46
46
|
"discoveryPolicySha256": "fba289aeeb5a3f11139b9589f7ab08b5e8c8799c7bdf8f0ea57ebfeb8e7c9da6",
|
|
47
47
|
"providerMaterializersSha256": "732e85b01a12e7dd19469e26e4427f0e0cfe5117a78fc9f69c2f0f89702f463a",
|
|
48
|
-
"providerLifecycleSha256": "
|
|
48
|
+
"providerLifecycleSha256": "43c6b2abb99a2b185eb143e5a5be12db0a3e096475cba58b1bde3a29dfb64940",
|
|
49
49
|
"providerInventorySha256": "00adf1c4f6b3367055a9cc3fdfe658660771b4c9931f696e12761a95076ad92c",
|
|
50
50
|
"executionRuntimeSha256": "3fca4ba4094e855412a22308e13f2b5f91dcfeb009512eb8318b3bd1bace8d9a",
|
|
51
51
|
"commonInstructionSha256": "3d40f4b5d3ea78f6c9ca5bd18daa531dc758d77df21a5e7ef35a3336cd71f906",
|
|
@@ -53,13 +53,13 @@
|
|
|
53
53
|
"claude": {
|
|
54
54
|
"instructionSha256": "f56a44c730506258cc7e2581dbdd31bfd1e6d13d60dc8a8ee8bcc398228453fb",
|
|
55
55
|
"hookSha256": "6398e6b20f6558392bdce1d7123815042047952be473b8fda6d2491aa1648a05",
|
|
56
|
-
"skillsSha256": "
|
|
56
|
+
"skillsSha256": "617994510c71b6cb717dd3f06dc2dbf2ea6bd68849b5be83ca6a624ac409f697",
|
|
57
57
|
"treeDigests": {}
|
|
58
58
|
},
|
|
59
59
|
"codex": {
|
|
60
60
|
"instructionSha256": "3d40f4b5d3ea78f6c9ca5bd18daa531dc758d77df21a5e7ef35a3336cd71f906",
|
|
61
61
|
"hookSha256": "697d186cfe0882ee2ce1b02ecc4c3258ab42b125bab6d70c7be868fa271132f7",
|
|
62
|
-
"skillsSha256": "
|
|
62
|
+
"skillsSha256": "a4f57953384153f393c3db3008f1967f637e0dc1d7cb149b8c47f020b62278ee",
|
|
63
63
|
"treeDigests": {}
|
|
64
64
|
},
|
|
65
65
|
"generic": {
|
|
@@ -72,9 +72,9 @@
|
|
|
72
72
|
"mergeSurfaceDigests": {
|
|
73
73
|
"claude": "6398e6b20f6558392bdce1d7123815042047952be473b8fda6d2491aa1648a05"
|
|
74
74
|
},
|
|
75
|
-
"governanceCheckSha256": "
|
|
76
|
-
"utilityRegistrySha256": "
|
|
77
|
-
"sharedSkillsSha256": "
|
|
75
|
+
"governanceCheckSha256": "393f5a016baac5a345e9c08a93c14bdb575b66570fd475a7fa86bc2e8002dd33",
|
|
76
|
+
"utilityRegistrySha256": "ac430d50ee82c19eb678f725c00eec23a0e25b33ec4bad81d3bf6e2fa9fa64ff",
|
|
77
|
+
"sharedSkillsSha256": "8b0a4563714a8b6226890f2a94b5fa28036960035e040c0224d4c127fc17efe6",
|
|
78
78
|
"sharedSkillCount": 11,
|
|
79
79
|
"requiredScripts": {
|
|
80
80
|
"audit:a11y": "node scripts/audit-consumer-a11y.mjs",
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
".github/workflows/audit.yml": "3cb259c8d83d8d378a6d282b0cde0a8884d96c51cf49e655405c3e416e646726",
|
|
94
94
|
".github/workflows/governance-anchor.yml": "3e280e67fd72886b0686a6d296947ee3270b4899e9914c1e3a9c61c951dcc2ee",
|
|
95
95
|
".npmrc": "79eff2d98653857c11ab6a0354fa62685a72ec214545e7fdfabc450e91b7ba63",
|
|
96
|
-
"governance/consumer-governance.md": "
|
|
96
|
+
"governance/consumer-governance.md": "bc58561386e0df62306a5d7246e51647847732efa3de84426bada9978ff6da5c",
|
|
97
97
|
"governance/provider-cli-toolchain.json": "6bc607cb6ac299b332d35ca871c838b54e367d96775ad0e759c827a0e3ca10cc",
|
|
98
98
|
"governance/provider-cli-toolchain.package-lock.json": "fe7115dcbb9c096cc9c2aff069282636b942995399ace5dfd9ba68a7280d465f",
|
|
99
99
|
"governance/schemas/issuer-registry.schema.json": "52616f59420942b96b33001e00d2c9a23ca3aa14dc236d8cb2aa9756787bfb90",
|
|
@@ -121,13 +121,13 @@
|
|
|
121
121
|
"scripts/schemas/product-template-scaffold-lock.schema.json": "1318af59f12546b3cda059a1d94923d9a969bef4c5a476ebb46e42f8c853d19a",
|
|
122
122
|
"scripts/schemas/release-bom.schema.json": "6969482588a25533dc1896be63c3c9629aced1cbeb745b99822db764682dc4a4",
|
|
123
123
|
"scripts/schemas/upgrade-evidence-receipt.schema.json": "e2da0a86241d8d019545b63e6285d2ca049ce95fe6aed4264149df2108c4ade0",
|
|
124
|
-
"scripts/setup-governance.mjs": "
|
|
124
|
+
"scripts/setup-governance.mjs": "2d5bf5ef149595854681c5744a431c2c231ead7d8699571a453783b39e556518",
|
|
125
125
|
"scripts/setup-provider-cli-toolchain.mjs": "b34e11d02a7aa71d001ef8f1a42af4cf97d1b8b82360f5779cb927370f5ee15a",
|
|
126
126
|
"scripts/setup-workspace.mjs": "95dc0fef1f7570efe277a66f7edf4640055ad6abbae0245615eceb9b041560e9",
|
|
127
127
|
"scripts/sync-all.mjs": "938c706cad79a4bd538359c9b74cb9253a4c74b5f1342cbfc1625600425d8bfd",
|
|
128
128
|
"scripts/verify-consumer-css-entry.mjs": "a16545ea8d4f5224d8133f0bb0b68d5489d8e106fe7594488a78e0e5718b99cc",
|
|
129
129
|
"scripts/verify-upgrade-evidence.mjs": "f4452640d34cb28dbddbca9919f4bc5d3caec7c35879f9e69fd5f6c8485b2ffc",
|
|
130
|
-
"scripts/verify-upgrade-provenance.mjs": "
|
|
130
|
+
"scripts/verify-upgrade-provenance.mjs": "1902716115df20d4b8831e7c497abc82f9d3b848a06ca6b7cc1fe64def37a31b",
|
|
131
131
|
"scripts/workflow-static-validation.mjs": "8e12bf2d73fc5e1200ddc6cc8437b27433fcd71f7d85d79b56756dcaac667305"
|
|
132
132
|
}
|
|
133
133
|
},
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
// Security boundary:
|
|
5
5
|
// verified npm/Sigstore bundle -> exact GitHub workflow/tag/commit
|
|
6
|
-
// ->
|
|
6
|
+
// -> exact GitHub lightweight commit tag or direct annotated tag object -> exact commit/tree
|
|
7
7
|
// -> immutable GitHub Release closed asset set (six core files plus optional finalizer evidence) -> receipt/trust/BOM/scaffold/package/SBOM bytes
|
|
8
8
|
// -> exact npm SRI.
|
|
9
9
|
// This file is loaded from the current trusted consumer before an incoming package is installed;
|
|
@@ -466,7 +466,7 @@ export function validateImmutableReleaseSnapshot({
|
|
|
466
466
|
policy,
|
|
467
467
|
expectedFinalizerRun = null,
|
|
468
468
|
// Lane selector, fail-closed by default:false = high-assurance(governance anchor /
|
|
469
|
-
// enterprise finalizer)— GitHub-verified signed tag + full 8-asset closure required。
|
|
469
|
+
// enterprise finalizer)— GitHub-verified signed annotated tag + full 8-asset closure required。
|
|
470
470
|
// true = ordinary consumer upgrade lane(tag→commit→tree + BOM + SLSA provenance;
|
|
471
471
|
// 簽章與 finalizer evidence assets 是 opt-in 加值)。Ordinary 入口必須顯式傳 true。
|
|
472
472
|
ordinaryRelease = false,
|
|
@@ -742,44 +742,61 @@ export async function resolveImmutableReleaseSnapshot({
|
|
|
742
742
|
`GitHub tag ${tag}`,
|
|
743
743
|
)
|
|
744
744
|
const reference = await referenceResponse.json()
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
||
|
|
748
|
-
|
|
745
|
+
const referenceType = reference?.object?.type
|
|
746
|
+
if (!reference
|
|
747
|
+
|| typeof reference !== 'object'
|
|
748
|
+
|| Array.isArray(reference)
|
|
749
|
+
|| reference.ref !== `refs/tags/${tag}`
|
|
750
|
+
|| !['commit', 'tag'].includes(referenceType)
|
|
751
|
+
|| !COMMIT_RE.test(reference.object?.sha || '')) {
|
|
752
|
+
throw new Error(`GitHub tag ${tag} must return one exact lightweight commit ref or annotated tag ref`)
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
const annotated = referenceType === 'tag'
|
|
756
|
+
if (!annotated && !ordinaryRelease) {
|
|
757
|
+
throw new Error(`GitHub tag ${tag} high-assurance lane requires a directly verified signed annotated tag object`)
|
|
749
758
|
}
|
|
750
759
|
const tagObjectSha = reference.object.sha
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
if (typeof verification.signature !== 'string' || !verification.signature
|
|
779
|
-
|| typeof verification.payload !== 'string' || !verification.payload) {
|
|
780
|
-
throw new Error(`GitHub tag ${tag} is not a directly verified signed annotated tag object`)
|
|
760
|
+
let commitSha
|
|
761
|
+
let signedAndValid = false
|
|
762
|
+
let verification = null
|
|
763
|
+
if (annotated) {
|
|
764
|
+
const tagObjectResponse = await fetchOk(
|
|
765
|
+
fetchImpl,
|
|
766
|
+
`${apiBase}/git/tags/${tagObjectSha}`,
|
|
767
|
+
{ headers: githubHeaders(token) },
|
|
768
|
+
`GitHub annotated tag ${tag}`,
|
|
769
|
+
)
|
|
770
|
+
const tagObject = await tagObjectResponse.json()
|
|
771
|
+
if (tagObject?.sha !== tagObjectSha
|
|
772
|
+
|| tagObject?.tag !== tag
|
|
773
|
+
|| tagObject?.object?.type !== 'commit'
|
|
774
|
+
|| !COMMIT_RE.test(tagObject.object.sha || '')) {
|
|
775
|
+
throw new Error(`GitHub tag ${tag} annotated tag object/name/direct commit binding is invalid`)
|
|
776
|
+
}
|
|
777
|
+
commitSha = tagObject.object.sha
|
|
778
|
+
verification = tagObject.verification
|
|
779
|
+
// An annotated ordinary release accepts signed-valid or explicitly unsigned tag objects.
|
|
780
|
+
// A broken signature claim (bad_signature etc.) still fails closed.
|
|
781
|
+
signedAndValid = verification?.verified === true && verification?.reason === 'valid'
|
|
782
|
+
const unsignedOrdinary = ordinaryRelease
|
|
783
|
+
&& verification?.verified === false
|
|
784
|
+
&& ['unsigned', 'unknown_signature_type'].includes(verification?.reason)
|
|
785
|
+
if (!signedAndValid && !unsignedOrdinary) {
|
|
786
|
+
throw new Error(`GitHub tag ${tag} is not an acceptable direct annotated tag object:${verification?.reason || '<missing>'}`)
|
|
781
787
|
}
|
|
782
|
-
|
|
788
|
+
if (signedAndValid) {
|
|
789
|
+
if (typeof verification.signature !== 'string' || !verification.signature
|
|
790
|
+
|| typeof verification.payload !== 'string' || !verification.payload) {
|
|
791
|
+
throw new Error(`GitHub tag ${tag} is not a directly verified signed annotated tag object`)
|
|
792
|
+
}
|
|
793
|
+
canonicalTimestamp(verification.verified_at, `GitHub tag ${tag} verified_at`)
|
|
794
|
+
}
|
|
795
|
+
} else {
|
|
796
|
+
// The formal release orchestrator publishes one exact lightweight tag. Its authority is the
|
|
797
|
+
// ref → commit → tree binding plus the immutable BOM and npm SLSA provenance; no tag object
|
|
798
|
+
// exists to carry a signature, so tagObject intentionally equals the direct commit object.
|
|
799
|
+
commitSha = tagObjectSha
|
|
783
800
|
}
|
|
784
801
|
const confirmedReferenceResponse = await fetchOk(
|
|
785
802
|
fetchImpl,
|
|
@@ -788,12 +805,14 @@ export async function resolveImmutableReleaseSnapshot({
|
|
|
788
805
|
`GitHub tag ${tag} confirmation`,
|
|
789
806
|
)
|
|
790
807
|
const confirmedReference = await confirmedReferenceResponse.json()
|
|
791
|
-
if (confirmedReference
|
|
792
|
-
|| confirmedReference
|
|
808
|
+
if (!confirmedReference
|
|
809
|
+
|| typeof confirmedReference !== 'object'
|
|
810
|
+
|| Array.isArray(confirmedReference)
|
|
811
|
+
|| confirmedReference.ref !== reference.ref
|
|
812
|
+
|| confirmedReference?.object?.type !== referenceType
|
|
793
813
|
|| confirmedReference.object.sha !== tagObjectSha) {
|
|
794
|
-
throw new Error(`GitHub tag ${tag} changed while its
|
|
814
|
+
throw new Error(`GitHub tag ${tag} changed while its exact object binding was being verified`)
|
|
795
815
|
}
|
|
796
|
-
const commitSha = tagObject.object.sha
|
|
797
816
|
const commitResponse = await fetchOk(
|
|
798
817
|
fetchImpl,
|
|
799
818
|
`${apiBase}/git/commits/${commitSha}`,
|
|
@@ -813,7 +832,15 @@ export async function resolveImmutableReleaseSnapshot({
|
|
|
813
832
|
tagObject: tagObjectSha,
|
|
814
833
|
commit: commitSha,
|
|
815
834
|
tree: commit.tree.sha,
|
|
816
|
-
verification:
|
|
835
|
+
verification: !annotated
|
|
836
|
+
? {
|
|
837
|
+
verified: false,
|
|
838
|
+
reason: 'unsigned',
|
|
839
|
+
verifiedAt: null,
|
|
840
|
+
signatureSha256: null,
|
|
841
|
+
payloadSha256: null,
|
|
842
|
+
}
|
|
843
|
+
: signedAndValid
|
|
817
844
|
? {
|
|
818
845
|
verified: true,
|
|
819
846
|
reason: 'valid',
|
|
@@ -54,21 +54,18 @@ authorizes its own next execution. Reviewed paths run from the DS authority, mat
|
|
|
54
54
|
fresh snapshot, execute no candidate code, and emit proposals rather than applying to the live
|
|
55
55
|
consumer or inventory.
|
|
56
56
|
|
|
57
|
-
##
|
|
57
|
+
## Release mirror and protected PR trust chain
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
write permissions. The Writer App may create or strictly reuse one deterministic upgrade branch and
|
|
64
|
-
PR; it cannot publish checks, approve, merge, bypass protection, or write directly to `main`.
|
|
59
|
+
Automatic template delivery is owned upstream by the release mirror. It opens a normal PR containing
|
|
60
|
+
the exact published snapshot; a consumer does not carry a second release-dispatch/updater workflow.
|
|
61
|
+
Repository Actions defaults remain read-only, and ordinary delivery requires no hosted environment or
|
|
62
|
+
App credential in the consumer.
|
|
65
63
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
`Immutable consumer snapshot` verdict. Review and protected rules decide whether the PR can merge.
|
|
64
|
+
Protected `main` requires the single `Verify consumer` context from `audit.yml`. That workflow performs
|
|
65
|
+
one locked install followed by typecheck, import lint, and build. The normal PR review and protected
|
|
66
|
+
branch rules decide whether the mirror proposal can merge. Preview, visual, accessibility, canary, and
|
|
67
|
+
external readback evidence are optional hardening lanes unless a repository explicitly opts into them;
|
|
68
|
+
they are not hidden prerequisites for the standard release path.
|
|
72
69
|
|
|
73
70
|
## Verification boundary
|
|
74
71
|
|
|
@@ -103,7 +103,7 @@ function sameFileIdentity(left, right) {
|
|
|
103
103
|
&& left.ctimeNs === right.ctimeNs
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
function
|
|
106
|
+
function stableRegularFile(path, label, maxBytes = MAX_SETUP_AUTHORITY_FILE_BYTES) {
|
|
107
107
|
let descriptor
|
|
108
108
|
try {
|
|
109
109
|
const beforePath = lstatSync(path, { bigint: true })
|
|
@@ -132,12 +132,19 @@ function stableRegularBytes(path, label, maxBytes = MAX_SETUP_AUTHORITY_FILE_BYT
|
|
|
132
132
|
&& BigInt(bytes.length) === before.size,
|
|
133
133
|
`${label} changed while it was captured`,
|
|
134
134
|
)
|
|
135
|
-
return
|
|
135
|
+
return {
|
|
136
|
+
bytes,
|
|
137
|
+
mode: Number(before.mode & 0o777n),
|
|
138
|
+
}
|
|
136
139
|
} finally {
|
|
137
140
|
if (descriptor !== undefined) closeSync(descriptor)
|
|
138
141
|
}
|
|
139
142
|
}
|
|
140
143
|
|
|
144
|
+
function stableRegularBytes(path, label, maxBytes = MAX_SETUP_AUTHORITY_FILE_BYTES) {
|
|
145
|
+
return stableRegularFile(path, label, maxBytes).bytes
|
|
146
|
+
}
|
|
147
|
+
|
|
141
148
|
function canonicalForkPath(value) {
|
|
142
149
|
return typeof value === 'string'
|
|
143
150
|
&& value.length > 0
|
|
@@ -155,8 +162,9 @@ function regularForkInventory(root) {
|
|
|
155
162
|
invariant(
|
|
156
163
|
directoryInfo.isDirectory()
|
|
157
164
|
&& !directoryInfo.isSymbolicLink()
|
|
165
|
+
&& (directoryInfo.mode & 0o777) === 0o755
|
|
158
166
|
&& realpathSync(directory) === directory,
|
|
159
|
-
`installed governance corpus directory is unsafe:${prefix || '.'}`,
|
|
167
|
+
`installed governance corpus directory is unsafe or has a non-canonical mode:${prefix || '.'}`,
|
|
160
168
|
)
|
|
161
169
|
for (const name of readdirSync(directory).sort()) {
|
|
162
170
|
invariant(name === name.normalize('NFC') && name !== '.' && name !== '..', `installed governance corpus name is not canonical:${name}`)
|
|
@@ -174,17 +182,27 @@ function regularForkInventory(root) {
|
|
|
174
182
|
return rows
|
|
175
183
|
}
|
|
176
184
|
|
|
177
|
-
function
|
|
185
|
+
function frozenSnapshotFileMode(sourceMode, label) {
|
|
186
|
+
invariant([0o644, 0o755].includes(sourceMode), `${label} has a non-canonical source mode`)
|
|
187
|
+
return sourceMode === 0o755 ? 0o500 : 0o400
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function writeExclusiveSnapshotFile(path, record, label) {
|
|
178
191
|
mkdirSync(dirname(path), { recursive: true, mode: 0o700 })
|
|
192
|
+
const frozenMode = frozenSnapshotFileMode(record.mode, label)
|
|
179
193
|
let descriptor
|
|
180
194
|
try {
|
|
181
|
-
descriptor = openSync(path, 'wx',
|
|
182
|
-
writeFileSync(descriptor, bytes)
|
|
195
|
+
descriptor = openSync(path, 'wx', frozenMode)
|
|
196
|
+
writeFileSync(descriptor, record.bytes)
|
|
183
197
|
fsyncSync(descriptor)
|
|
184
198
|
} finally {
|
|
185
199
|
if (descriptor !== undefined) closeSync(descriptor)
|
|
186
200
|
}
|
|
187
|
-
|
|
201
|
+
const written = stableRegularFile(path, `private checker snapshot ${path}`)
|
|
202
|
+
invariant(
|
|
203
|
+
written.mode === frozenMode && sha256(written.bytes) === sha256(record.bytes),
|
|
204
|
+
'private checker snapshot write did not preserve authenticated bytes and frozen source-mode identity',
|
|
205
|
+
)
|
|
188
206
|
}
|
|
189
207
|
|
|
190
208
|
function freezeSnapshotDirectories(root) {
|
|
@@ -223,7 +241,8 @@ function materializeInstalledCheckerClosure(root) {
|
|
|
223
241
|
&& realpathSync(installedFork) === installedFork,
|
|
224
242
|
'installed governance corpus root is missing or has symlink ancestry',
|
|
225
243
|
)
|
|
226
|
-
const
|
|
244
|
+
const corpusLockRecord = stableRegularFile(join(installedFork, 'governance.lock'), 'installed governance corpus lock')
|
|
245
|
+
const corpusLockBytes = corpusLockRecord.bytes
|
|
227
246
|
let corpusLock
|
|
228
247
|
let committedBom
|
|
229
248
|
try {
|
|
@@ -243,10 +262,12 @@ function materializeInstalledCheckerClosure(root) {
|
|
|
243
262
|
committedBom?.payload?.forkCorpusLockSha256 === sha256(corpusLockBytes),
|
|
244
263
|
'committed governance lock does not authenticate the installed corpus lock',
|
|
245
264
|
)
|
|
246
|
-
const
|
|
265
|
+
const shippedBomRecord = stableRegularFile(join(installedFork, 'consumer/lock.json'), 'installed governance release lock')
|
|
266
|
+
const shippedBom = shippedBomRecord.bytes
|
|
247
267
|
const committedBomBytes = stableRegularBytes(join(root, 'governance/lock.json'), 'committed governance lock')
|
|
248
268
|
invariant(shippedBom.equals(committedBomBytes), 'installed governance release lock differs from the committed lock')
|
|
249
|
-
const
|
|
269
|
+
const shippedSchemaRecord = stableRegularFile(join(installedFork, 'consumer/lock.schema.json'), 'installed governance lock schema')
|
|
270
|
+
const shippedSchema = shippedSchemaRecord.bytes
|
|
250
271
|
const committedSchema = stableRegularBytes(join(root, 'governance/lock.schema.json'), 'committed governance lock schema')
|
|
251
272
|
invariant(shippedSchema.equals(committedSchema), 'installed governance lock schema differs from the committed schema')
|
|
252
273
|
|
|
@@ -262,16 +283,17 @@ function materializeInstalledCheckerClosure(root) {
|
|
|
262
283
|
&& !entries.has(entry.file),
|
|
263
284
|
`installed governance corpus lock entry is invalid or duplicated:${index}`,
|
|
264
285
|
)
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
286
|
+
const record = stableRegularFile(join(installedFork, ...entry.file.split('/')), `installed governance corpus ${entry.file}`)
|
|
287
|
+
frozenSnapshotFileMode(record.mode, `installed governance corpus ${entry.file}`)
|
|
288
|
+
invariant(sha256(record.bytes) === entry.sha256, `installed governance corpus digest mismatch:${entry.file}`)
|
|
289
|
+
totalBytes += record.bytes.length
|
|
268
290
|
invariant(totalBytes <= MAX_SETUP_AUTHORITY_TOTAL_BYTES, 'installed governance corpus exceeds the closed snapshot budget')
|
|
269
|
-
entries.set(entry.file,
|
|
291
|
+
entries.set(entry.file, record)
|
|
270
292
|
}
|
|
271
293
|
const checkerRelative = 'consumer/governance-check.mjs'
|
|
272
294
|
invariant(
|
|
273
295
|
entries.has(checkerRelative)
|
|
274
|
-
&& committedBom?.payload?.governanceCheckSha256 === sha256(entries.get(checkerRelative)),
|
|
296
|
+
&& committedBom?.payload?.governanceCheckSha256 === sha256(entries.get(checkerRelative).bytes),
|
|
275
297
|
'committed governance lock does not authenticate the installed checker',
|
|
276
298
|
)
|
|
277
299
|
const expectedInventory = [...entries.keys(), 'consumer/lock.json', 'consumer/lock.schema.json', 'governance.lock'].sort()
|
|
@@ -281,9 +303,9 @@ function materializeInstalledCheckerClosure(root) {
|
|
|
281
303
|
)
|
|
282
304
|
const sourceClosure = new Map([
|
|
283
305
|
...entries,
|
|
284
|
-
['governance.lock',
|
|
285
|
-
['consumer/lock.json',
|
|
286
|
-
['consumer/lock.schema.json',
|
|
306
|
+
['governance.lock', corpusLockRecord],
|
|
307
|
+
['consumer/lock.json', shippedBomRecord],
|
|
308
|
+
['consumer/lock.schema.json', shippedSchemaRecord],
|
|
287
309
|
])
|
|
288
310
|
|
|
289
311
|
const privateBase = resolveClosedPrivateRuntimeBase(process.platform)
|
|
@@ -292,12 +314,12 @@ function materializeInstalledCheckerClosure(root) {
|
|
|
292
314
|
const snapshotFork = join(snapshotRoot, 'fork')
|
|
293
315
|
mkdirSync(snapshotFork, { mode: 0o700 })
|
|
294
316
|
try {
|
|
295
|
-
for (const [path,
|
|
296
|
-
writeExclusiveSnapshotFile(join(snapshotFork, ...path.split('/')),
|
|
317
|
+
for (const [path, record] of entries) {
|
|
318
|
+
writeExclusiveSnapshotFile(join(snapshotFork, ...path.split('/')), record, `installed governance corpus ${path}`)
|
|
297
319
|
}
|
|
298
|
-
writeExclusiveSnapshotFile(join(snapshotFork, 'governance.lock'),
|
|
299
|
-
writeExclusiveSnapshotFile(join(snapshotFork, 'consumer/lock.json'),
|
|
300
|
-
writeExclusiveSnapshotFile(join(snapshotFork, 'consumer/lock.schema.json'),
|
|
320
|
+
writeExclusiveSnapshotFile(join(snapshotFork, 'governance.lock'), corpusLockRecord, 'installed governance corpus lock')
|
|
321
|
+
writeExclusiveSnapshotFile(join(snapshotFork, 'consumer/lock.json'), shippedBomRecord, 'installed governance release lock')
|
|
322
|
+
writeExclusiveSnapshotFile(join(snapshotFork, 'consumer/lock.schema.json'), shippedSchemaRecord, 'installed governance lock schema')
|
|
301
323
|
freezeSnapshotDirectories(snapshotRoot)
|
|
302
324
|
return {
|
|
303
325
|
checker: join(snapshotFork, checkerRelative),
|
|
@@ -310,11 +332,12 @@ function materializeInstalledCheckerClosure(root) {
|
|
|
310
332
|
'installed governance corpus inventory changed after checker snapshot',
|
|
311
333
|
)
|
|
312
334
|
for (const [path, expected] of sourceClosure) {
|
|
335
|
+
const current = stableRegularFile(
|
|
336
|
+
join(installedFork, ...path.split('/')),
|
|
337
|
+
`installed checker closure CAS ${path}`,
|
|
338
|
+
)
|
|
313
339
|
invariant(
|
|
314
|
-
|
|
315
|
-
join(installedFork, ...path.split('/')),
|
|
316
|
-
`installed checker closure CAS ${path}`,
|
|
317
|
-
).equals(expected),
|
|
340
|
+
current.mode === expected.mode && current.bytes.equals(expected.bytes),
|
|
318
341
|
`installed checker closure changed after authenticated snapshot:${path}`,
|
|
319
342
|
)
|
|
320
343
|
}
|