@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.
Files changed (57) hide show
  1. package/ds-canonical/fork/codex/agents/skills/code-quality-audit/SKILL.md +2 -2
  2. package/ds-canonical/fork/codex/agents/skills/product-ui-audit/references/report-template.md +1 -1
  3. package/ds-canonical/fork/codex/agents/skills/scan-similar-bugs/SKILL.md +1 -1
  4. package/ds-canonical/fork/codex/agents/skills/visual-audit/SKILL.md +1 -1
  5. package/ds-canonical/fork/consumer/governance-check.mjs +20 -4
  6. package/ds-canonical/fork/consumer/lock.json +12 -12
  7. package/ds-canonical/fork/consumer/managed-files/794bb911ba8ebef58ab0d44a55b9968a19d2c62bcfb310b1ee3a84166f087f21.blob +69 -42
  8. package/ds-canonical/fork/consumer/managed-files/884721550b56308812b20917adac7a12f36fdb4ecda6b02460a7f5a219c0c966.blob +10 -13
  9. package/ds-canonical/fork/consumer/managed-files/bc44cabad181f766611dab6a8f52d81100afadd50ac59799015e8ba7bf46d9eb.blob +50 -27
  10. package/ds-canonical/fork/governance.lock +34 -26
  11. package/ds-canonical/fork/hooks/check_consumer_app_invariants.sh +211 -1
  12. package/ds-canonical/fork/hooks/check_escape_marker_abuse.sh +17 -15
  13. package/ds-canonical/fork/hooks/check_layout_space_magic_numbers.sh +22 -1
  14. package/ds-canonical/fork/hooks/lib/_micro_geometry.sh +291 -0
  15. package/ds-canonical/fork/manifest.json +10 -10
  16. package/ds-canonical/fork/providers/claude/skills/code-quality-audit/SKILL.md +2 -2
  17. package/ds-canonical/fork/providers/claude/skills/product-ui-audit/references/report-template.md +1 -1
  18. package/ds-canonical/fork/providers/claude/skills/scan-similar-bugs/SKILL.md +1 -1
  19. package/ds-canonical/fork/providers/claude/skills/visual-audit/SKILL.md +1 -1
  20. package/ds-canonical/fork/providers/codex/skills/code-quality-audit/SKILL.md +2 -2
  21. package/ds-canonical/fork/providers/codex/skills/product-ui-audit/references/report-template.md +1 -1
  22. package/ds-canonical/fork/providers/codex/skills/scan-similar-bugs/SKILL.md +1 -1
  23. package/ds-canonical/fork/providers/codex/skills/visual-audit/SKILL.md +1 -1
  24. package/ds-canonical/fork/references/utility-registry.json +50 -1
  25. package/ds-canonical/fork/references/utility-registry.schema.json +37 -1
  26. package/ds-canonical/fork/skills/code-quality-audit/SKILL.md +2 -2
  27. package/ds-canonical/fork/skills/product-ui-audit/references/report-template.md +1 -1
  28. package/ds-canonical/fork/skills/scan-similar-bugs/SKILL.md +1 -1
  29. package/ds-canonical/fork/skills/visual-audit/SKILL.md +1 -1
  30. package/ds-canonical/hooks/check_consumer_app_invariants.sh +211 -1
  31. package/ds-canonical/hooks/check_escape_marker_abuse.sh +17 -15
  32. package/ds-canonical/hooks/check_layout_space_magic_numbers.sh +22 -1
  33. package/ds-canonical/hooks/lib/_micro_geometry.sh +291 -0
  34. package/ds-canonical/hooks/tests/test_check_consumer_ds_primitive_misuse.sh +80 -0
  35. package/ds-canonical/hooks/tests/test_check_escape_marker_abuse.sh +52 -0
  36. package/ds-canonical/hooks/tests/test_check_layout_space_magic_numbers.sh +58 -0
  37. package/ds-canonical/references/build-ui-canonicals.md +13 -0
  38. package/ds-canonical/references/ssot-index.md +1 -1
  39. package/ds-canonical/rules/meta-patterns.md +1 -1
  40. package/ds-canonical/skills/code-quality-audit/SKILL.md +2 -2
  41. package/ds-canonical/skills/codify-corrections/SKILL.md +4 -4
  42. package/ds-canonical/skills/codify-principle/SKILL.md +2 -2
  43. package/ds-canonical/skills/design-system-audit/SKILL.md +4 -4
  44. package/ds-canonical/skills/design-system-audit/references/audit-prompts.md +4 -2
  45. package/ds-canonical/skills/design-system-audit/references/principle-audit-protocol.md +1 -1
  46. package/ds-canonical/skills/product-ui-audit/references/report-template.md +1 -1
  47. package/ds-canonical/skills/scan-similar-bugs/SKILL.md +1 -1
  48. package/ds-canonical/skills/story-writing/SKILL.md +1 -1
  49. package/ds-canonical/skills/visual-audit/SKILL.md +1 -1
  50. package/ds-canonical/templates/consumer-governance.md +10 -13
  51. package/package.json +1 -1
  52. package/src/components/BulkActionBar/bulk-action-bar.principles.stories.tsx +3 -3
  53. package/src/components/Button/button.principles.stories.tsx +2 -2
  54. package/src/components/Chart/chart.principles.stories.tsx +2 -2
  55. package/src/tokens/layoutSpace/layoutSpace.spec.md +1 -1
  56. package/src/tokens/utility-registry.json +50 -1
  57. 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/independent review;不得用泛用 user
49
- approval 代替工程判斷。
48
+ - 無法分類時 fail closed 並補 source/evidence;task/deliverable 明確要求時才加
49
+ independent review,不得用泛用 user approval 代替工程判斷。
50
50
 
51
51
  ## 禁止
52
52
 
@@ -47,7 +47,7 @@ scope / 設計決策 / 業務判斷:
47
47
 
48
48
  **處理**:
49
49
  - **P2E**:engineering/governance/refactor/a11y/performance 或既有 SSOT 唯一落地,依
50
- evidence + independent review + hard gates 自主修。
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 + independent review逐檔收斂
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、independent review 與 hard gates 收斂;無法收斂則 fail closed 回報 technical blocker
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 mode = (stat.mode & 0o777).toString(8).padStart(4, '0')
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 actualRows = treeInventory(actual)
286
- const canonicalRows = treeInventory(canonical)
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.104",
7
- "storybookConfig": "0.1.0-beta.104"
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": "dd68020ddcc2980e47d17b0db09cfcce5c593f29382270cc6328395ce9fc3285",
45
+ "forkCorpusLockSha256": "0d2489d8bb989a6cd7c039f2c7262deb6fcd0be26e67a21ede951003da953455",
46
46
  "discoveryPolicySha256": "fba289aeeb5a3f11139b9589f7ab08b5e8c8799c7bdf8f0ea57ebfeb8e7c9da6",
47
47
  "providerMaterializersSha256": "732e85b01a12e7dd19469e26e4427f0e0cfe5117a78fc9f69c2f0f89702f463a",
48
- "providerLifecycleSha256": "d68d37b0c362b38ac70bffd135aeea6eb483d078db49e68cc69ec3ea7bae01d8",
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": "f5767bb43b442af15cbe45307365388953552c6b57b8fd58cfabe391b73fe228",
56
+ "skillsSha256": "617994510c71b6cb717dd3f06dc2dbf2ea6bd68849b5be83ca6a624ac409f697",
57
57
  "treeDigests": {}
58
58
  },
59
59
  "codex": {
60
60
  "instructionSha256": "3d40f4b5d3ea78f6c9ca5bd18daa531dc758d77df21a5e7ef35a3336cd71f906",
61
61
  "hookSha256": "697d186cfe0882ee2ce1b02ecc4c3258ab42b125bab6d70c7be868fa271132f7",
62
- "skillsSha256": "f2f34252fa8bc0be9ddd6a7ca2a7243a184f1b1a951f98f1f2e9773850573a3e",
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": "363b6af5c1e9c87cc11a53bb9868cb17e591c64f5545a09dc66f66e86f70e66f",
76
- "utilityRegistrySha256": "310f92cc9aa57880d8f81464ac4191907bda9c9af31847a450497e6a9f97dce3",
77
- "sharedSkillsSha256": "9a79b48a6543929a7b94952c1bd3be90faa15d3dc963e8487df0dd6bb8474aa7",
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": "abbded0a31cf3a6fb0de62975b749d10f27ac0adcc9bbf5e95ef410b64073396",
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": "5297a0c6dace974c1233a95825a6ffceef8823373eb4bb96947961f3979e4339",
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": "be8be9b993199d0eb01da1b57cd0e395a4bfceea70a78ff45e436f626c290ed3",
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
- // -> direct GitHub-verified signed annotated tag object -> exact commit/tree
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
- if (reference?.ref !== `refs/tags/${tag}`
746
- || reference?.object?.type !== 'tag'
747
- || !COMMIT_RE.test(reference.object.sha || '')) {
748
- throw new Error(`GitHub tag ${tag} must directly reference one annotated tag object; lightweight tags are forbidden`)
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
- const tagObjectResponse = await fetchOk(
752
- fetchImpl,
753
- `${apiBase}/git/tags/${tagObjectSha}`,
754
- { headers: githubHeaders(token) },
755
- `GitHub annotated tag ${tag}`,
756
- )
757
- const tagObject = await tagObjectResponse.json()
758
- if (tagObject?.sha !== tagObjectSha
759
- || tagObject?.tag !== tag
760
- || tagObject?.object?.type !== 'commit'
761
- || !COMMIT_RE.test(tagObject.object.sha || '')) {
762
- throw new Error(`GitHub tag ${tag} annotated tag object/name/direct commit binding is invalid`)
763
- }
764
- const verification = tagObject.verification
765
- // Ordinary releases push an annotated tag whose authority comes from the exact
766
- // tag commit tree binding plus the BOM digests and npm SLSA provenance.
767
- // A GitHub-verified signature (verified === true, reason 'valid') is the opt-in
768
- // high-assurance finalizer's extra guarantee, not an ordinary requirement — but a
769
- // BROKEN signature claim (bad_signature etc. on a signed tag) still fails closed.
770
- const signedAndValid = verification?.verified === true && verification?.reason === 'valid'
771
- const unsignedOrdinary = ordinaryRelease
772
- && verification?.verified === false
773
- && ['unsigned', 'unknown_signature_type'].includes(verification?.reason)
774
- if (!signedAndValid && !unsignedOrdinary) {
775
- throw new Error(`GitHub tag ${tag} is not a directly verified signed annotated tag object:${verification?.reason || '<missing>'}`)
776
- }
777
- if (signedAndValid) {
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
- canonicalTimestamp(verification.verified_at, `GitHub tag ${tag} verified_at`)
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?.ref !== reference.ref
792
- || confirmedReference?.object?.type !== 'tag'
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 signed annotated object was being verified`)
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: signedAndValid
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
- ## Automated upgrade trust chain
57
+ ## Release mirror and protected PR trust chain
58
58
 
59
- Repository Actions defaults stay read-only, and **Allow GitHub Actions to create and approve pull
60
- requests** stays disabled. The credential-free certifier and fresh read-only verifier first bind the
61
- exact base, paths, patch, tree, and incoming bytes. Only the protected-branch `governance-upgrade`
62
- environment may mint the short-lived Governance Writer App token with contents and pull-request
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
- Writer App pushes can start ordinary candidate workflows. Those automatic candidate runs are
67
- non-authoritative and never satisfy the required check. The writer must explicitly send the
68
- `governance-upgrade-candidate-validation` repository dispatch. That event reloads
69
- `governance-anchor.yml` from protected default, executes candidate verification without credentials,
70
- and lets only the separate check-only Governance Check App publish the required
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 stableRegularBytes(path, label, maxBytes = MAX_SETUP_AUTHORITY_FILE_BYTES) {
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 bytes
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 writeExclusiveSnapshotFile(path, bytes) {
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', 0o400)
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
- invariant(sha256(stableRegularBytes(path, `private checker snapshot ${path}`)) === sha256(bytes), 'private checker snapshot write did not preserve authenticated bytes')
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 corpusLockBytes = stableRegularBytes(join(installedFork, 'governance.lock'), 'installed governance corpus lock')
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 shippedBom = stableRegularBytes(join(installedFork, 'consumer/lock.json'), 'installed governance release lock')
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 shippedSchema = stableRegularBytes(join(installedFork, 'consumer/lock.schema.json'), 'installed governance lock schema')
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 bytes = stableRegularBytes(join(installedFork, ...entry.file.split('/')), `installed governance corpus ${entry.file}`)
266
- invariant(sha256(bytes) === entry.sha256, `installed governance corpus digest mismatch:${entry.file}`)
267
- totalBytes += bytes.length
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, bytes)
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', corpusLockBytes],
285
- ['consumer/lock.json', shippedBom],
286
- ['consumer/lock.schema.json', shippedSchema],
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, bytes] of entries) {
296
- writeExclusiveSnapshotFile(join(snapshotFork, ...path.split('/')), bytes)
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'), corpusLockBytes)
299
- writeExclusiveSnapshotFile(join(snapshotFork, 'consumer/lock.json'), shippedBom)
300
- writeExclusiveSnapshotFile(join(snapshotFork, 'consumer/lock.schema.json'), shippedSchema)
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
- stableRegularBytes(
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
  }