@qijenchen/design-system 0.1.0-beta.108 → 0.1.0-beta.110

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 (54) hide show
  1. package/dist/components/AccountMenu/account-menu.d.ts.map +1 -1
  2. package/dist/components/AccountMenu/account-menu.js +2 -1
  3. package/dist/components/AccountMenu/account-menu.js.map +1 -1
  4. package/dist/patterns/element-anatomy/item-anatomy.js +1 -1
  5. package/ds-canonical/adapters/claude-settings-base.json +1 -1
  6. package/ds-canonical/fork/AGENTS.md +1 -1
  7. package/ds-canonical/fork/codex/agents/skills/independent-review/references/evidence-contract.md +8 -9
  8. package/ds-canonical/fork/codex/hooks.json +1 -1
  9. package/ds-canonical/fork/common/closed-tool-execution.mjs +5 -1
  10. package/ds-canonical/fork/common/instruction.md +1 -1
  11. package/ds-canonical/fork/consumer/governance-check.mjs +1 -1
  12. package/ds-canonical/fork/consumer/lib/closed-tool-execution.mjs +5 -1
  13. package/ds-canonical/fork/consumer/lock.json +18 -18
  14. package/ds-canonical/fork/consumer/managed-files/2d1ef5617a5ce3c39ad9aae1c42af7d199f9270c0a0ccb490b63d62dbc6913db.blob +5 -1
  15. package/ds-canonical/fork/consumer/managed-files/46f10bc55cdfdf834d0749eebff42ea0bb8576f361b36d2b6ba1851354c50150.blob +65 -16
  16. package/ds-canonical/fork/consumer/managed-files/63061e05969667005020166ea2ae7df3c96663f7a928e7dd3eebc1471022df56.blob +1 -1
  17. package/ds-canonical/fork/consumer/managed-files/7eb6cc60abfdabaf6617c336058fad1454ccde08889295767bfd9b7fd153cc6e.blob +3 -3
  18. package/ds-canonical/fork/governance.lock +21 -21
  19. package/ds-canonical/fork/hooks/check_pattern_invariants.sh +5 -0
  20. package/ds-canonical/fork/launchers/approval-evidence.mjs +38 -2
  21. package/ds-canonical/fork/launchers/provider-hook-output-transport.mjs +15 -0
  22. package/ds-canonical/fork/launchers/settings-hooks.json +2 -2
  23. package/ds-canonical/fork/manifest.json +11 -10
  24. package/ds-canonical/fork/providers/claude/hook-config.json +1 -1
  25. package/ds-canonical/fork/providers/claude/skills/independent-review/references/evidence-contract.md +8 -9
  26. package/ds-canonical/fork/providers/codex/hook-config.json +1 -1
  27. package/ds-canonical/fork/providers/codex/skills/independent-review/references/evidence-contract.md +8 -9
  28. package/ds-canonical/fork/skills/independent-review/references/evidence-contract.md +8 -9
  29. package/ds-canonical/hooks/check_audit_post_report_validator.sh +62 -2
  30. package/ds-canonical/hooks/check_benchmark_citation.sh +5 -0
  31. package/ds-canonical/hooks/check_main_branch_workbench.sh +1 -1
  32. package/ds-canonical/hooks/check_pattern_invariants.sh +5 -0
  33. package/ds-canonical/hooks/check_propose_discipline.sh +25 -4
  34. package/ds-canonical/hooks/check_solo_workflow.sh +1 -1
  35. package/ds-canonical/hooks/check_substantive_edit_approval_preflight.sh +2 -2
  36. package/ds-canonical/hooks/lib/_hardcoded_strings.sh +5 -0
  37. package/ds-canonical/hooks/lib/approval-evidence.mjs +38 -2
  38. package/ds-canonical/hooks/session_start_governance_check.sh +7 -4
  39. package/ds-canonical/hooks/tests/test_check_substantive_edit_approval_preflight.sh +4 -2
  40. package/ds-canonical/references/certified-surfaces.md +5 -10
  41. package/ds-canonical/references/repository-hygiene-policy.json +1 -1
  42. package/ds-canonical/references/repository-hygiene.md +10 -0
  43. package/ds-canonical/rules/meta-patterns.md +1 -1
  44. package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +4 -2
  45. package/ds-canonical/skills/design-system-audit/references/audit-prompts.md +11 -3
  46. package/ds-canonical/skills/independent-review/references/evidence-contract.md +8 -9
  47. package/ds-canonical/skills/new-component/SKILL.md +1 -1
  48. package/ds-canonical/templates/product-provider-wiring.json +1 -0
  49. package/ds-canonical/templates/product-provider-wiring.schema.json +3 -2
  50. package/package.json +1 -1
  51. package/src/components/AccountMenu/account-menu.anatomy.stories.tsx +3 -1
  52. package/src/components/AccountMenu/account-menu.tsx +6 -2
  53. package/src/components/Tag/tag.spec.md +1 -1
  54. package/ds-canonical/hooks/tests/KNOWN-BROKEN.md +0 -15
@@ -1 +1 @@
1
- {"version":3,"file":"account-menu.d.ts","sourceRoot":"","sources":["../../../src/components/AccountMenu/account-menu.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAU,KAAK,UAAU,EAAE,MAAM,0CAA0C,CAAA;AAYlF,MAAM,WAAW,eAAe;IAC9B,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAA;IACZ;;;;OAIG;IACH,MAAM,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,WAAW,GAAG,KAAK,CAAC,GAAG;QAAE,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAClE;AAED,MAAM,WAAW,gBAAgB;IAC/B,cAAc;IACd,IAAI,EAAE,eAAe,CAAA;IACrB,oFAAoF;IACpF,aAAa,CAAC,EAAE,MAAM,IAAI,CAAA;IAC1B,qCAAqC;IACrC,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;IAC3B,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,8EAA8E;IAC9E,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAA;IAClC,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,gCAAgC;IAChC,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,+FAA+F;IAC/F,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,yCAAyC;IACzC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACtC;;;;OAIG;IACH,YAAY,CAAC,EAAE,IAAI,CACjB,KAAK,CAAC,wBAAwB,CAAC,QAAQ,CAAC,EACxC,UAAU,GAAG,MAAM,GAAG,YAAY,GAAG,WAAW,GAAG,UAAU,CAC9D,CAAA;CACF;AAID,eAAO,MAAM,WAAW,4FA6EvB,CAAA;AAKD,eAAO,MAAM,eAAe;;;;;;;;;;;CAWlB,CAAA"}
1
+ {"version":3,"file":"account-menu.d.ts","sourceRoot":"","sources":["../../../src/components/AccountMenu/account-menu.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAU,KAAK,UAAU,EAAE,MAAM,0CAA0C,CAAA;AAYlF,MAAM,WAAW,eAAe;IAC9B,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAA;IACZ;;;;OAIG;IACH,MAAM,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,WAAW,GAAG,KAAK,CAAC,GAAG;QAAE,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAClE;AAED,MAAM,WAAW,gBAAgB;IAC/B,cAAc;IACd,IAAI,EAAE,eAAe,CAAA;IACrB,oFAAoF;IACpF,aAAa,CAAC,EAAE,MAAM,IAAI,CAAA;IAC1B,qCAAqC;IACrC,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;IAC3B,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,8EAA8E;IAC9E,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAA;IAClC,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,gCAAgC;IAChC,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,+FAA+F;IAC/F,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,yCAAyC;IACzC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACtC;;;;OAIG;IACH,YAAY,CAAC,EAAE,IAAI,CACjB,KAAK,CAAC,wBAAwB,CAAC,QAAQ,CAAC,EACxC,UAAU,GAAG,MAAM,GAAG,YAAY,GAAG,WAAW,GAAG,UAAU,CAC9D,CAAA;CACF;AAID,eAAO,MAAM,WAAW,4FAiFvB,CAAA;AAKD,eAAO,MAAM,eAAe;;;;;;;;;;;CAWlB,CAAA"}
@@ -12,6 +12,7 @@ const AccountMenu = React.forwardRef(
12
12
  children,
13
13
  align = "end",
14
14
  triggerAriaLabel = "帳號與設定",
15
+ // i18n-allow: spec 預設 aria-label(account-menu.spec.md),consumer 以同名 prop 覆蓋
15
16
  open,
16
17
  defaultOpen,
17
18
  onOpenChange,
@@ -31,7 +32,7 @@ const AccountMenu = React.forwardRef(
31
32
  ref,
32
33
  type: "button",
33
34
  "aria-label": triggerAriaLabel,
34
- className: "flex items-center justify-center rounded-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
35
+ className: "flex size-6 shrink-0 items-center justify-center self-center rounded-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
35
36
  children: /* @__PURE__ */ jsx(Avatar, { size: 24, ...avatar, alt: avatar.alt ?? user.name })
36
37
  }
37
38
  ) }),
@@ -1 +1 @@
1
- {"version":3,"file":"account-menu.js","sources":["../../../src/components/AccountMenu/account-menu.tsx"],"sourcesContent":["/**\n * AccountMenu — 全域 chrome「自己的帳號入口」(24px avatar 觸發 + 帳號選單)\n *\n * ── 定位 ──\n * Primary-header 派 globalHeader 右側(desktop)/ mobile Sheet SidebarHeader 右側的\n * 帳號入口成品元件:點 avatar 開「個人資料 / 設定 / 登出」navigation 選單。\n * 是「自己」的入口,不是看「別人」的 ProfileCard(後者預設動作 Chat/通話,語義是聯絡某人)。\n * **Placement-agnostic**:哪個 mode 放哪裡是 consumer 職責,放置 canonical owner =\n * `../AppShell/app-shell.spec.md`「帳號入口(Account entry)放置 SSOT」(本元件不收 mode prop,\n * 不複製該 SSOT;consumer 讀 `useAppShell().layout` + `useSidebar().isMobile` 決定放置)。\n *\n * ── 實作基礎 ──\n * 消費:Avatar(raw size=24 trigger)+ DropdownMenu(Trigger asChild / Content / Group / Label / Item)\n * 對應 pattern:patterns/header-canonical(4.5 chrome header avatar SSOT)\n *\n * ── 消費的 SSOT ──\n * - components: [Avatar, DropdownMenu(DropdownMenuTrigger/Content/Group/Label/Item)]\n * - patterns: [header-canonical 4.5(chrome header avatar = raw `<Avatar size={24}>`,非 ItemAvatar)]\n * - tokens: [--chrome-header-avatar-size(JS 端 24 literal sync), --ring(focus-visible ring canonical)]\n * - spec refs: app-shell.spec.md「帳號入口(Account entry)放置 SSOT」/ header-canonical.spec.md 4.5\n * / dropdown-menu.spec.md / profile-card.spec.md(近親分界:看別人 vs 自己)\n *\n * 升級歷程:2026-06-17 `AppShell/_demo-helpers.tsx` demo helper 誕生(user directive)→\n * 2026-07-30 F9 升 public:app-shell.spec.md + sidebar.spec.md 兩處 canonical 都 mandate\n * 消費本元件,「文件教得到、import 拿不到」= DT-EXPORT 斷鏈同款缺口(gen-component-indexes.mjs\n * 檔頭 anchor),npm consumer 被迫整包重造。世界級對照:Atlassian `@atlaskit/atlassian-navigation`\n * 出貨專門 `Profile` 元件作 top bar 帳號 trigger(ProfileProps = IconButtonProps,選單內容\n * composition;https://unpkg.com/@atlaskit/atlassian-navigation/dist/types/index.d.ts +\n * dist/types/components/Profile/types.d.ts);shadcn / Primer 走 DropdownMenu / ActionMenu\n * composition recipe(https://ui.shadcn.com/docs/components/dropdown-menu「An account switcher\n * dropdown triggered by an avatar」/ https://primer.style/components/action-menu)。本元件 =\n * Atlassian 專門元件 + shadcn canonical 內容組(Label(user)+ Groups)的收斂。\n */\nimport * as React from 'react'\nimport { User, Settings, LogOut } from 'lucide-react'\nimport { Avatar, type AvatarData } from '@/design-system/components/Avatar/avatar'\nimport {\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuGroup,\n} from '@/design-system/components/DropdownMenu/dropdown-menu'\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\nexport interface AccountMenuUser {\n /** 顯示名(選單 Label 區;alt 未傳時同時作 avatar alt fallback) */\n name: string\n /**\n * Avatar 資料(消費既有 `AvatarData` type)。`hoverCard` 刻意排除:帳號入口是「自己」,\n * 不掛 ProfileCard(per app-shell.spec.md 帳號入口 SSOT「不用 ProfileCard」);\n * `alt` 可省,fallback `name`。\n */\n avatar?: Omit<AvatarData, 'hoverCard' | 'alt'> & { alt?: string }\n}\n\nexport interface AccountMenuProps {\n /** 當前登入使用者 */\n user: AccountMenuUser\n /** 「個人資料」item handler(default 選單消費;命名對齊 `PersonData.onViewProfile` 既有 canonical) */\n onViewProfile?: () => void\n /** 「設定」item handler(default 選單消費) */\n onOpenSettings?: () => void\n /** 「登出」item handler(default 選單消費) */\n onSignOut?: () => void\n /**\n * 取代 default 選單內容(自組 `DropdownMenuGroup` / `DropdownMenuItem`;i18n / 產品自訂項走這)。\n * Identity Label(user.name)恆由本元件渲染於選單頂(對齊 shadcn account 例 Label(user)慣例);\n * 傳入 children 時三個 default callback 不再消費。\n */\n children?: React.ReactNode\n /** 選單對齊(轉傳 DropdownMenuContent `align`)。預設 `'end'`:右上入口靠右展開(demo baseline) */\n align?: 'start' | 'center' | 'end'\n /** Trigger aria-label(i18n override,對齊 Avatar `badgeAriaLabel` pattern)。預設「帳號與設定」 */\n triggerAriaLabel?: string\n /** 受控展開狀態(Radix passthrough) */\n open?: boolean\n /** 預設展開(uncontrolled,Radix passthrough)。Storybook OpenSnapshot(M15 visual-audit coverable)用 */\n defaultOpen?: boolean\n /** 展開狀態變更 callback(Radix passthrough) */\n onOpenChange?: (open: boolean) => void\n /**\n * Trigger button 的非視覺 DOM attributes / event handlers。\n * `children`、`type`、`aria-label`、`className`、`disabled` 由 AccountMenu canonical contract\n * 擁有;分析與測試所需的 `data-*`、`aria-*`、事件可由此傳入。\n */\n triggerProps?: Omit<\n React.ComponentPropsWithoutRef<'button'>,\n 'children' | 'type' | 'aria-label' | 'className' | 'disabled'\n >\n}\n\n// ── AccountMenu ─────────────────────────────────────────────────────────────\n\nexport const AccountMenu = React.forwardRef<HTMLButtonElement, AccountMenuProps>(\n ({\n user,\n onViewProfile,\n onOpenSettings,\n onSignOut,\n children,\n align = 'end',\n triggerAriaLabel = '帳號與設定',\n open,\n defaultOpen,\n onOpenChange,\n triggerProps,\n }, ref) => {\n // Dev-mode warning(對齊 ProfileCardDefaultActions dev-warn 先例):default 選單項沒接任何\n // handler = item 點了永遠沒反應;production 必接 callback 或自組 children。\n if (\n process.env.NODE_ENV !== 'production' &&\n !children &&\n !onViewProfile &&\n !onOpenSettings &&\n !onSignOut\n ) {\n // eslint-disable-next-line no-console\n console.warn(\n '[DS AccountMenu] default 選單項未接 onViewProfile/onOpenSettings/onSignOut——item 點擊無反應。請傳入 callback 或自組 children。'\n )\n }\n const avatar = user.avatar ?? {}\n return (\n <DropdownMenu open={open} defaultOpen={defaultOpen} onOpenChange={onOpenChange}>\n <DropdownMenuTrigger asChild>\n {/* 互動感由 focus-visible ring 提供(無 hover bg,chrome 輕量 entry),不放大到 field height\n — per app-shell.spec.md「Avatar 尺寸 + 邊距」+ header-canonical.spec.md 4.5 */}\n <button\n {...triggerProps}\n ref={ref}\n type=\"button\"\n aria-label={triggerAriaLabel}\n className=\"flex items-center justify-center rounded-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1\"\n >\n {/* 24 per header-canonical.spec.md 4.5 chrome header avatar canonical(brand + account 同尺寸); sync with --chrome-header-avatar-size */}\n <Avatar size={24} {...avatar} alt={avatar.alt ?? user.name} />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align={align}>\n {children ? (\n <>\n <DropdownMenuGroup>\n <DropdownMenuLabel>{user.name}</DropdownMenuLabel>\n </DropdownMenuGroup>\n {children}\n </>\n ) : (\n <>\n {/* Default 選單集合 = app-shell.spec.md「入口開什麼」canonical(個人資料 / 設定 / 登出,\n 登出獨立 group — 破壞性動作分區,對齊 GitHub / Gmail / Slack 帳號選單慣例) */}\n <DropdownMenuGroup>\n <DropdownMenuLabel>{user.name}</DropdownMenuLabel>\n <DropdownMenuItem startIcon={User} onSelect={onViewProfile}>\n 個人資料\n </DropdownMenuItem>\n <DropdownMenuItem startIcon={Settings} onSelect={onOpenSettings}>\n 設定\n </DropdownMenuItem>\n </DropdownMenuGroup>\n <DropdownMenuGroup>\n <DropdownMenuItem startIcon={LogOut} onSelect={onSignOut}>\n 登出\n </DropdownMenuItem>\n </DropdownMenuGroup>\n </>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n },\n)\nAccountMenu.displayName = 'AccountMenu'\n\n// Story auto-compile metadata — F9 public 升級(2026-07-30)\n// Phase 2 fill needed: purpose descriptions + when rationale\nexport const accountMenuMeta = {\n component: 'AccountMenu',\n family: null, // composite(trigger = chrome header 24px avatar;浮層 = DropdownMenu Family 1 消費者)\n variants: {},\n sizes: {}, // trigger avatar 24px density-fixed(header-canonical.spec.md 4.5),無 size 軸 → 兩欄留空為 intentional\n states: ['default', 'focus-visible'],\n tokens: {\n bg: [],\n fg: [],\n ring: ['--ring'], // trigger focus-visible ring canonical\n },\n} as const\n"],"names":[],"mappings":";;;;;AAgGO,MAAM,cAAc,MAAM;AAAA,EAC/B,CAAC;AAAA,IACC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR,mBAAmB;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,GACC,QAAQ;AAGT,QACE,QAAQ,IAAI,aAAa,gBACzB,CAAC,YACD,CAAC,iBACD,CAAC,kBACD,CAAC,WACD;AAEA,cAAQ;AAAA,QACN;AAAA,MAAA;AAAA,IAEJ;AACA,UAAM,SAAS,KAAK,UAAU,CAAA;AAC9B,WACE,qBAAC,cAAA,EAAa,MAAY,aAA0B,cAClD,UAAA;AAAA,MAAA,oBAAC,qBAAA,EAAoB,SAAO,MAG1B,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACE,GAAG;AAAA,UACJ;AAAA,UACA,MAAK;AAAA,UACL,cAAY;AAAA,UACZ,WAAU;AAAA,UAGV,UAAA,oBAAC,QAAA,EAAO,MAAM,IAAK,GAAG,QAAQ,KAAK,OAAO,OAAO,KAAK,KAAA,CAAM;AAAA,QAAA;AAAA,MAAA,GAEhE;AAAA,MACA,oBAAC,qBAAA,EAAoB,OAClB,UAAA,WACC,qBAAA,UAAA,EACE,UAAA;AAAA,QAAA,oBAAC,mBAAA,EACC,UAAA,oBAAC,mBAAA,EAAmB,UAAA,KAAK,MAAK,GAChC;AAAA,QACC;AAAA,MAAA,EAAA,CACH,IAEA,qBAAA,UAAA,EAGE,UAAA;AAAA,QAAA,qBAAC,mBAAA,EACC,UAAA;AAAA,UAAA,oBAAC,mBAAA,EAAmB,eAAK,KAAA,CAAK;AAAA,8BAC7B,kBAAA,EAAiB,WAAW,MAAM,UAAU,eAAe,UAAA,QAE5D;AAAA,8BACC,kBAAA,EAAiB,WAAW,UAAU,UAAU,gBAAgB,UAAA,KAAA,CAEjE;AAAA,QAAA,GACF;AAAA,QACA,oBAAC,qBACC,UAAA,oBAAC,kBAAA,EAAiB,WAAW,QAAQ,UAAU,WAAW,UAAA,KAAA,CAE1D,EAAA,CACF;AAAA,MAAA,EAAA,CACF,EAAA,CAEJ;AAAA,IAAA,GACF;AAAA,EAEJ;AACF;AACA,YAAY,cAAc;AAInB,MAAM,kBAAkB;AAAA,EAC7B,WAAW;AAAA,EACX,QAAQ;AAAA;AAAA,EACR,UAAU,CAAA;AAAA,EACV,OAAO,CAAA;AAAA;AAAA,EACP,QAAQ,CAAC,WAAW,eAAe;AAAA,EACnC,QAAQ;AAAA,IACN,IAAI,CAAA;AAAA,IACJ,IAAI,CAAA;AAAA,IACJ,MAAM,CAAC,QAAQ;AAAA;AAAA,EAAA;AAEnB;"}
1
+ {"version":3,"file":"account-menu.js","sources":["../../../src/components/AccountMenu/account-menu.tsx"],"sourcesContent":["/**\n * AccountMenu — 全域 chrome「自己的帳號入口」(24px avatar 觸發 + 帳號選單)\n *\n * ── 定位 ──\n * Primary-header 派 globalHeader 右側(desktop)/ mobile Sheet SidebarHeader 右側的\n * 帳號入口成品元件:點 avatar 開「個人資料 / 設定 / 登出」navigation 選單。\n * 是「自己」的入口,不是看「別人」的 ProfileCard(後者預設動作 Chat/通話,語義是聯絡某人)。\n * **Placement-agnostic**:哪個 mode 放哪裡是 consumer 職責,放置 canonical owner =\n * `../AppShell/app-shell.spec.md`「帳號入口(Account entry)放置 SSOT」(本元件不收 mode prop,\n * 不複製該 SSOT;consumer 讀 `useAppShell().layout` + `useSidebar().isMobile` 決定放置)。\n *\n * ── 實作基礎 ──\n * 消費:Avatar(raw size=24 trigger)+ DropdownMenu(Trigger asChild / Content / Group / Label / Item)\n * 對應 pattern:patterns/header-canonical(4.5 chrome header avatar SSOT)\n *\n * ── 消費的 SSOT ──\n * - components: [Avatar, DropdownMenu(DropdownMenuTrigger/Content/Group/Label/Item)]\n * - patterns: [header-canonical 4.5(chrome header avatar = raw `<Avatar size={24}>`,非 ItemAvatar)]\n * - tokens: [--chrome-header-avatar-size(JS 端 24 literal sync), --ring(focus-visible ring canonical)]\n * - spec refs: app-shell.spec.md「帳號入口(Account entry)放置 SSOT」/ header-canonical.spec.md 4.5\n * / dropdown-menu.spec.md / profile-card.spec.md(近親分界:看別人 vs 自己)\n *\n * 升級歷程:2026-06-17 `AppShell/_demo-helpers.tsx` demo helper 誕生(user directive)→\n * 2026-07-30 F9 升 public:app-shell.spec.md + sidebar.spec.md 兩處 canonical 都 mandate\n * 消費本元件,「文件教得到、import 拿不到」= DT-EXPORT 斷鏈同款缺口(gen-component-indexes.mjs\n * 檔頭 anchor),npm consumer 被迫整包重造。世界級對照:Atlassian `@atlaskit/atlassian-navigation`\n * 出貨專門 `Profile` 元件作 top bar 帳號 trigger(ProfileProps = IconButtonProps,選單內容\n * composition;https://unpkg.com/@atlaskit/atlassian-navigation/dist/types/index.d.ts +\n * dist/types/components/Profile/types.d.ts);shadcn / Primer 走 DropdownMenu / ActionMenu\n * composition recipe(https://ui.shadcn.com/docs/components/dropdown-menu「An account switcher\n * dropdown triggered by an avatar」/ https://primer.style/components/action-menu)。本元件 =\n * Atlassian 專門元件 + shadcn canonical 內容組(Label(user)+ Groups)的收斂。\n */\nimport * as React from 'react'\nimport { User, Settings, LogOut } from 'lucide-react'\nimport { Avatar, type AvatarData } from '@/design-system/components/Avatar/avatar'\nimport {\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuGroup,\n} from '@/design-system/components/DropdownMenu/dropdown-menu'\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\nexport interface AccountMenuUser {\n /** 顯示名(選單 Label 區;alt 未傳時同時作 avatar alt fallback) */\n name: string\n /**\n * Avatar 資料(消費既有 `AvatarData` type)。`hoverCard` 刻意排除:帳號入口是「自己」,\n * 不掛 ProfileCard(per app-shell.spec.md 帳號入口 SSOT「不用 ProfileCard」);\n * `alt` 可省,fallback `name`。\n */\n avatar?: Omit<AvatarData, 'hoverCard' | 'alt'> & { alt?: string }\n}\n\nexport interface AccountMenuProps {\n /** 當前登入使用者 */\n user: AccountMenuUser\n /** 「個人資料」item handler(default 選單消費;命名對齊 `PersonData.onViewProfile` 既有 canonical) */\n onViewProfile?: () => void\n /** 「設定」item handler(default 選單消費) */\n onOpenSettings?: () => void\n /** 「登出」item handler(default 選單消費) */\n onSignOut?: () => void\n /**\n * 取代 default 選單內容(自組 `DropdownMenuGroup` / `DropdownMenuItem`;i18n / 產品自訂項走這)。\n * Identity Label(user.name)恆由本元件渲染於選單頂(對齊 shadcn account 例 Label(user)慣例);\n * 傳入 children 時三個 default callback 不再消費。\n */\n children?: React.ReactNode\n /** 選單對齊(轉傳 DropdownMenuContent `align`)。預設 `'end'`:右上入口靠右展開(demo baseline) */\n align?: 'start' | 'center' | 'end'\n /** Trigger aria-label(i18n override,對齊 Avatar `badgeAriaLabel` pattern)。預設「帳號與設定」 */\n triggerAriaLabel?: string\n /** 受控展開狀態(Radix passthrough) */\n open?: boolean\n /** 預設展開(uncontrolled,Radix passthrough)。Storybook OpenSnapshot(M15 visual-audit coverable)用 */\n defaultOpen?: boolean\n /** 展開狀態變更 callback(Radix passthrough) */\n onOpenChange?: (open: boolean) => void\n /**\n * Trigger button 的非視覺 DOM attributes / event handlers。\n * `children`、`type`、`aria-label`、`className`、`disabled` 由 AccountMenu canonical contract\n * 擁有;分析與測試所需的 `data-*`、`aria-*`、事件可由此傳入。\n */\n triggerProps?: Omit<\n React.ComponentPropsWithoutRef<'button'>,\n 'children' | 'type' | 'aria-label' | 'className' | 'disabled'\n >\n}\n\n// ── AccountMenu ─────────────────────────────────────────────────────────────\n\nexport const AccountMenu = React.forwardRef<HTMLButtonElement, AccountMenuProps>(\n ({\n user,\n onViewProfile,\n onOpenSettings,\n onSignOut,\n children,\n align = 'end',\n triggerAriaLabel = '帳號與設定', // i18n-allow: spec 預設 aria-label(account-menu.spec.md),consumer 以同名 prop 覆蓋\n open,\n defaultOpen,\n onOpenChange,\n triggerProps,\n }, ref) => {\n // Dev-mode warning(對齊 ProfileCardDefaultActions dev-warn 先例):default 選單項沒接任何\n // handler = item 點了永遠沒反應;production 必接 callback 或自組 children。\n if (\n process.env.NODE_ENV !== 'production' &&\n !children &&\n !onViewProfile &&\n !onOpenSettings &&\n !onSignOut\n ) {\n // eslint-disable-next-line no-console\n console.warn(\n '[DS AccountMenu] default 選單項未接 onViewProfile/onOpenSettings/onSignOut——item 點擊無反應。請傳入 callback 或自組 children。'\n )\n }\n const avatar = user.avatar ?? {}\n return (\n <DropdownMenu open={open} defaultOpen={defaultOpen} onOpenChange={onOpenChange}>\n <DropdownMenuTrigger asChild>\n {/* 互動感由 focus-visible ring 提供(無 hover bg,chrome 輕量 entry),不放大到 field height\n — per app-shell.spec.md「Avatar 尺寸 + 邊距」+ header-canonical.spec.md 4.5 */}\n {/* size-6 = Avatar 24 的幾何鎖:trigger 是 Radix 的 anchor box,沒鎖尺寸時 flex 預設\n align-items: stretch 會把按鈕撐高(實測 min-h-32 容器 → 96px anchor → 選單視覺間距\n 44px,遠離 elevation.spec.md 的 8px canonical)。鎖死後 anchor ≡ avatar,任何容器\n 都維持 sideOffset=8 的視覺意圖;shrink-0 防壓縮對偶。user 2026-08-04 拍板。 */}\n <button\n {...triggerProps}\n ref={ref}\n type=\"button\"\n aria-label={triggerAriaLabel}\n className=\"flex size-6 shrink-0 items-center justify-center self-center rounded-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1\"\n >\n {/* 24 per header-canonical.spec.md 4.5 chrome header avatar canonical(brand + account 同尺寸); sync with --chrome-header-avatar-size */}\n <Avatar size={24} {...avatar} alt={avatar.alt ?? user.name} />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align={align}>\n {children ? (\n <>\n <DropdownMenuGroup>\n <DropdownMenuLabel>{user.name}</DropdownMenuLabel>\n </DropdownMenuGroup>\n {children}\n </>\n ) : (\n <>\n {/* Default 選單集合 = app-shell.spec.md「入口開什麼」canonical(個人資料 / 設定 / 登出,\n 登出獨立 group — 破壞性動作分區,對齊 GitHub / Gmail / Slack 帳號選單慣例) */}\n <DropdownMenuGroup>\n <DropdownMenuLabel>{user.name}</DropdownMenuLabel>\n <DropdownMenuItem startIcon={User} onSelect={onViewProfile}>\n 個人資料\n </DropdownMenuItem>\n <DropdownMenuItem startIcon={Settings} onSelect={onOpenSettings}>\n 設定\n </DropdownMenuItem>\n </DropdownMenuGroup>\n <DropdownMenuGroup>\n <DropdownMenuItem startIcon={LogOut} onSelect={onSignOut}>\n 登出\n </DropdownMenuItem>\n </DropdownMenuGroup>\n </>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n },\n)\nAccountMenu.displayName = 'AccountMenu'\n\n// Story auto-compile metadata — F9 public 升級(2026-07-30)\n// Phase 2 fill needed: purpose descriptions + when rationale\nexport const accountMenuMeta = {\n component: 'AccountMenu',\n family: null, // composite(trigger = chrome header 24px avatar;浮層 = DropdownMenu Family 1 消費者)\n variants: {},\n sizes: {}, // trigger avatar 24px density-fixed(header-canonical.spec.md 4.5),無 size 軸 → 兩欄留空為 intentional\n states: ['default', 'focus-visible'],\n tokens: {\n bg: [],\n fg: [],\n ring: ['--ring'], // trigger focus-visible ring canonical\n },\n} as const\n"],"names":[],"mappings":";;;;;AAgGO,MAAM,cAAc,MAAM;AAAA,EAC/B,CAAC;AAAA,IACC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR,mBAAmB;AAAA;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,GACC,QAAQ;AAGT,QACE,QAAQ,IAAI,aAAa,gBACzB,CAAC,YACD,CAAC,iBACD,CAAC,kBACD,CAAC,WACD;AAEA,cAAQ;AAAA,QACN;AAAA,MAAA;AAAA,IAEJ;AACA,UAAM,SAAS,KAAK,UAAU,CAAA;AAC9B,WACE,qBAAC,cAAA,EAAa,MAAY,aAA0B,cAClD,UAAA;AAAA,MAAA,oBAAC,qBAAA,EAAoB,SAAO,MAO1B,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACE,GAAG;AAAA,UACJ;AAAA,UACA,MAAK;AAAA,UACL,cAAY;AAAA,UACZ,WAAU;AAAA,UAGV,UAAA,oBAAC,QAAA,EAAO,MAAM,IAAK,GAAG,QAAQ,KAAK,OAAO,OAAO,KAAK,KAAA,CAAM;AAAA,QAAA;AAAA,MAAA,GAEhE;AAAA,MACA,oBAAC,qBAAA,EAAoB,OAClB,UAAA,WACC,qBAAA,UAAA,EACE,UAAA;AAAA,QAAA,oBAAC,mBAAA,EACC,UAAA,oBAAC,mBAAA,EAAmB,UAAA,KAAK,MAAK,GAChC;AAAA,QACC;AAAA,MAAA,EAAA,CACH,IAEA,qBAAA,UAAA,EAGE,UAAA;AAAA,QAAA,qBAAC,mBAAA,EACC,UAAA;AAAA,UAAA,oBAAC,mBAAA,EAAmB,eAAK,KAAA,CAAK;AAAA,8BAC7B,kBAAA,EAAiB,WAAW,MAAM,UAAU,eAAe,UAAA,QAE5D;AAAA,8BACC,kBAAA,EAAiB,WAAW,UAAU,UAAU,gBAAgB,UAAA,KAAA,CAEjE;AAAA,QAAA,GACF;AAAA,QACA,oBAAC,qBACC,UAAA,oBAAC,kBAAA,EAAiB,WAAW,QAAQ,UAAU,WAAW,UAAA,KAAA,CAE1D,EAAA,CACF;AAAA,MAAA,EAAA,CACF,EAAA,CAEJ;AAAA,IAAA,GACF;AAAA,EAEJ;AACF;AACA,YAAY,cAAc;AAInB,MAAM,kBAAkB;AAAA,EAC7B,WAAW;AAAA,EACX,QAAQ;AAAA;AAAA,EACR,UAAU,CAAA;AAAA,EACV,OAAO,CAAA;AAAA;AAAA,EACP,QAAQ,CAAC,WAAW,eAAe;AAAA,EACnC,QAAQ;AAAA,IACN,IAAI,CAAA;AAAA,IACJ,IAAI,CAAA;AAAA,IACJ,MAAM,CAAC,QAAQ;AAAA;AAAA,EAAA;AAEnB;"}
@@ -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 { cn, lineClampClass } from "../../lib/utils.js";
4
+ import { lineClampClass, cn } 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";
@@ -161,7 +161,7 @@
161
161
  ],
162
162
  "allowPlaintextInject": false
163
163
  },
164
- "enableWeakerNestedSandbox": false,
164
+ "enableWeakerNestedSandbox": true,
165
165
  "enableWeakerNetworkIsolation": false,
166
166
  "allowAppleEvents": false,
167
167
  "excludedCommands": []
@@ -22,7 +22,7 @@
22
22
  - Never install a mutable tag or semver range for internal packages. Upgrades are reviewed pull
23
23
  requests that pin an exact version and include the regenerated lock/adapters.
24
24
  <!-- canonical-decision-authority:start -->
25
- **Decision/Engineering Authority**:user 只拍板產品/UI/UX SSOT 真取捨及可感知/產品語意變更(behavior/interaction/IA/visual/token/layout/content/a11y/canonical rules);核准須同 scope exact target/choice/operation digest,引用/條件/舊 scope/跨 target 無效。其餘工程/external writes 皆 Standing Authorization AUTO,含已核准 UI/UX 實作/機械 generation/sync 與 source→commit/PR/merge→canonical `infra/governance/release-workflow.json` 的 `pr-checks → merge → publish → readback → consumer`;依 frozen scope、SSOT、required checks、security、least privilege、rollback/readback 收斂,不逐 milestone 重問。Deep Audit 必須在單一 branch/PR 完成 remediation 與 local/CI candidate validation,禁止把 immutable publish 當 iteration/test loop,每次 audit 最多一次 final release;只有另有 evidence ref 綁定 incident ID、failure class、published version 的 post-publish blocker 或 security incident 才可額外 release。Certification、rollout、staged rollout、preview/canary 與 independent review 是明確要求時的附加 assurance,不得進入標準 five-step release blocking graph;peer 不可用只阻擋明確要求的 independent-review claim。
25
+ **Decision/Engineering Authority**:user 只拍板產品/UI/UX SSOT 真取捨及可感知/產品語意變更(behavior/interaction/IA/visual/token/layout/content/a11y/canonical rules);核准 = user 在對話中對 exact target + choice 說可(**「我說可以就是可以,就是授權」— user 2026-08-04 verbatim**;最新一則 user 訊息的明確 blanket 授權即核准當下 pending 的 exact 提案)。operation digest 為可選佐證——有引且相符可加強、有引但不符 fail-closed、未引不阻擋(強制引 digest 屬已拆除的 per-PR 簽章同族儀式,835b519e 先例)。引用/條件/舊 scope/跨 target 無效。其餘工程/external writes 皆 Standing Authorization AUTO,含已核准 UI/UX 實作/機械 generation/sync 與 source→commit/PR/merge→canonical `infra/governance/release-workflow.json` 的 `pr-checks → merge → publish → readback → consumer`;依 frozen scope、SSOT、required checks、security、least privilege、rollback/readback 收斂,不逐 milestone 重問。Deep Audit 必須在單一 branch/PR 完成 remediation 與 local/CI candidate validation,禁止把 immutable publish 當 iteration/test loop,每次 audit 最多一次 final release;只有另有 evidence ref 綁定 incident ID、failure class、published version 的 post-publish blocker 或 security incident 才可額外 release。Certification、rollout、staged rollout、preview/canary 與 independent review 是明確要求時的附加 assurance,不得進入標準 five-step release blocking graph;peer 不可用只阻擋明確要求的 independent-review claim。
26
26
 
27
27
  **Visual baseline**:user 對 exact image set/UI/UX 語意說「可以改」即拍板;Agent 自動 apply/generate/test/commit/PR/CI/merge,禁再核准/key enrollment/簽章。僅 user 明確要求 independent cryptographic review 才啟用 `visual-baseline-review-policy.json`,否則不阻擋。
28
28
 
@@ -1,16 +1,15 @@
1
1
  # Independent-review evidence contract
2
2
 
3
- Use the transport and evidence broker selected by the generated provider binding. Do not invent a provider command, copy credentials into the repository, or create a second broker schema.
3
+ Use the transport selected by the generated provider binding. Do not invent a provider command, copy credentials into the repository, or create a second evidence schema.
4
4
 
5
- When managed broker evidence is requested, accept it only after the existing content-addressed broker and deep-audit evidence validators have accepted the exact receipt. The authority-owned implementations are `infra/governance/model-evidence-broker.json`, `scripts/lib/model-evidence-broker.mjs`, and `scripts/lib/deep-audit-evidence-contract.mjs`; a product reviewer consumes their validated receipt but never edits, publishes, or executes authority release controls. If that validated receipt is unavailable, use `LOCAL-REVIEW-ONLY` or `REVIEW-BLOCKED`, never managed or certified evidence.
5
+ The model-broker execution layer (evidence broker, shard/transcript carriers, model runner, managed-CI executors) was retired 2026-08-04; broker or managed receipts are permanently rejected. The surviving authority-owned contract surfaces are `scripts/lib/deep-audit-evidence-contract.mjs` (waived self-review and entitlement review-lane validation) and `scripts/lib/model-api-transport.mjs` (pure invocation-profile and response-substitution contract). A product reviewer consumes their validated receipt but never edits, publishes, or executes authority release controls. If a validated receipt is unavailable, use `LOCAL-REVIEW-ONLY` or `REVIEW-BLOCKED`, never managed or certified evidence.
6
6
 
7
- The shipped product launcher is deliberately plan-only. A caller-supplied JavaScript callback, a
8
- self-declared model string, or structurally valid result JSON is not transport attestation and can
9
- never yield `REVIEW-VALIDATED`. Every carrier must use the canonical
10
- `content-addressed-model-broker-exchange-v1` exporter/importer and its exact selection, request,
11
- result, coverage, and reduction receipts. Managed broker execution is an optional deployment
12
- wrapper over that same exchange core; only its independently verified runtime/attestation evidence
13
- can upgrade a structural reduction to managed or promotion-eligible evidence.
7
+ A caller-supplied JavaScript callback, a self-declared model string, or structurally valid result
8
+ JSON is not transport attestation and can never yield `REVIEW-VALIDATED`. An independent-review
9
+ claim requires the entitlement review lane: a resolved provider binding whose peer, profile, and
10
+ entitlement readback are certified for the exact target, with response substitution fail-closed.
11
+ Without that binding the only honest outcomes are the waived self-review route (recorded waiver,
12
+ self-attested coverage, `unverifiedModelCoverage` visible) or `REVIEW-BLOCKED`.
14
13
 
15
14
  Emit one closed review record in the response with:
16
15
 
@@ -1,5 +1,5 @@
1
1
  {
2
- "description": "_generated: build-fork-governance.mjs; provider=codex; sourceDigest=e84847f981a2ef08a3520bea3d26c56b78c8d883231b0aba750f5a41ba2485f1; exact npm corpus; native hooks are feedback only, provider-neutral check + protected CI are authoritative.",
2
+ "description": "_generated: build-fork-governance.mjs; provider=codex; sourceDigest=2cc48406238101fac213dcef260be8e289bf829275230cc8b77ccc08104935d1; exact npm corpus; native hooks are feedback only, provider-neutral check + protected CI are authoritative.",
3
3
  "hooks": {
4
4
  "SessionStart": [
5
5
  {
@@ -66,12 +66,14 @@ const CLOSED_HOOK_EXECUTABLE_CANDIDATES = Object.freeze({
66
66
  bash: Object.freeze(['/bin/bash']),
67
67
  git: Object.freeze(['/usr/bin/git']),
68
68
  jq: Object.freeze(['/usr/bin/jq']),
69
+ perl: Object.freeze(['/usr/bin/perl']),
69
70
  python3: Object.freeze(['/usr/bin/python3']),
70
71
  }),
71
72
  linux: Object.freeze({
72
73
  bash: Object.freeze(['/usr/bin/bash', '/bin/bash']),
73
74
  git: Object.freeze(['/usr/bin/git']),
74
75
  jq: Object.freeze(['/usr/bin/jq']),
76
+ perl: Object.freeze(['/usr/bin/perl']),
75
77
  python3: Object.freeze(['/usr/bin/python3']),
76
78
  }),
77
79
  })
@@ -717,7 +719,9 @@ export function materializeClosedHookToolProfile({
717
719
  )
718
720
  const canonicalRepo = canonicalRepositoryRoot(repoRoot)
719
721
  const executableRecords = Object.fromEntries(
720
- ['bash', 'git', 'jq', 'node', 'python3'].map((label) => [
722
+ // perl added 2026-08-04: three canonical hooks and the hardcoded-strings lib shell out to it,
723
+ // so the pinned tool profile has to declare it like every other hook interpreter.
724
+ ['bash', 'git', 'jq', 'node', 'perl', 'python3'].map((label) => [
721
725
  label,
722
726
  resolveClosedHookExecutable({
723
727
  label,
@@ -22,7 +22,7 @@
22
22
  - Never install a mutable tag or semver range for internal packages. Upgrades are reviewed pull
23
23
  requests that pin an exact version and include the regenerated lock/adapters.
24
24
  <!-- canonical-decision-authority:start -->
25
- **Decision/Engineering Authority**:user 只拍板產品/UI/UX SSOT 真取捨及可感知/產品語意變更(behavior/interaction/IA/visual/token/layout/content/a11y/canonical rules);核准須同 scope exact target/choice/operation digest,引用/條件/舊 scope/跨 target 無效。其餘工程/external writes 皆 Standing Authorization AUTO,含已核准 UI/UX 實作/機械 generation/sync 與 source→commit/PR/merge→canonical `infra/governance/release-workflow.json` 的 `pr-checks → merge → publish → readback → consumer`;依 frozen scope、SSOT、required checks、security、least privilege、rollback/readback 收斂,不逐 milestone 重問。Deep Audit 必須在單一 branch/PR 完成 remediation 與 local/CI candidate validation,禁止把 immutable publish 當 iteration/test loop,每次 audit 最多一次 final release;只有另有 evidence ref 綁定 incident ID、failure class、published version 的 post-publish blocker 或 security incident 才可額外 release。Certification、rollout、staged rollout、preview/canary 與 independent review 是明確要求時的附加 assurance,不得進入標準 five-step release blocking graph;peer 不可用只阻擋明確要求的 independent-review claim。
25
+ **Decision/Engineering Authority**:user 只拍板產品/UI/UX SSOT 真取捨及可感知/產品語意變更(behavior/interaction/IA/visual/token/layout/content/a11y/canonical rules);核准 = user 在對話中對 exact target + choice 說可(**「我說可以就是可以,就是授權」— user 2026-08-04 verbatim**;最新一則 user 訊息的明確 blanket 授權即核准當下 pending 的 exact 提案)。operation digest 為可選佐證——有引且相符可加強、有引但不符 fail-closed、未引不阻擋(強制引 digest 屬已拆除的 per-PR 簽章同族儀式,835b519e 先例)。引用/條件/舊 scope/跨 target 無效。其餘工程/external writes 皆 Standing Authorization AUTO,含已核准 UI/UX 實作/機械 generation/sync 與 source→commit/PR/merge→canonical `infra/governance/release-workflow.json` 的 `pr-checks → merge → publish → readback → consumer`;依 frozen scope、SSOT、required checks、security、least privilege、rollback/readback 收斂,不逐 milestone 重問。Deep Audit 必須在單一 branch/PR 完成 remediation 與 local/CI candidate validation,禁止把 immutable publish 當 iteration/test loop,每次 audit 最多一次 final release;只有另有 evidence ref 綁定 incident ID、failure class、published version 的 post-publish blocker 或 security incident 才可額外 release。Certification、rollout、staged rollout、preview/canary 與 independent review 是明確要求時的附加 assurance,不得進入標準 five-step release blocking graph;peer 不可用只阻擋明確要求的 independent-review claim。
26
26
 
27
27
  **Visual baseline**:user 對 exact image set/UI/UX 語意說「可以改」即拍板;Agent 自動 apply/generate/test/commit/PR/CI/merge,禁再核准/key enrollment/簽章。僅 user 明確要求 independent cryptographic review 才啟用 `visual-baseline-review-policy.json`,否則不阻擋。
28
28
 
@@ -1254,7 +1254,7 @@ const executionRuntimeValid = Boolean(
1254
1254
  && executionRuntime.nativeWindowsPolicy === 'unsupported-fail-closed'
1255
1255
  && JSON.stringify(executionRuntime.supportedHostPlatforms) === JSON.stringify(['darwin', 'linux'])
1256
1256
  && JSON.stringify(executionRuntime.windowsCompatibilityEnvironments) === JSON.stringify(['devcontainer-linux', 'wsl2-linux'])
1257
- && JSON.stringify(executionRuntime.requiredExecutables) === JSON.stringify(['bash', 'git', 'jq', 'node', 'python3'])
1257
+ && JSON.stringify(executionRuntime.requiredExecutables) === JSON.stringify(['bash', 'git', 'jq', 'node', 'perl', 'python3'])
1258
1258
  && JSON.stringify(executionRuntime.requiredPathClasses) === JSON.stringify(['plain', 'space-containing'])
1259
1259
  )
1260
1260
  if (!executionRuntimeValid) add('GOV-RUNTIME-001', 'BLOCKER', 'installed consumer execution runtime contract is missing, open-shaped, or unsupported')
@@ -66,12 +66,14 @@ const CLOSED_HOOK_EXECUTABLE_CANDIDATES = Object.freeze({
66
66
  bash: Object.freeze(['/bin/bash']),
67
67
  git: Object.freeze(['/usr/bin/git']),
68
68
  jq: Object.freeze(['/usr/bin/jq']),
69
+ perl: Object.freeze(['/usr/bin/perl']),
69
70
  python3: Object.freeze(['/usr/bin/python3']),
70
71
  }),
71
72
  linux: Object.freeze({
72
73
  bash: Object.freeze(['/usr/bin/bash', '/bin/bash']),
73
74
  git: Object.freeze(['/usr/bin/git']),
74
75
  jq: Object.freeze(['/usr/bin/jq']),
76
+ perl: Object.freeze(['/usr/bin/perl']),
75
77
  python3: Object.freeze(['/usr/bin/python3']),
76
78
  }),
77
79
  })
@@ -717,7 +719,9 @@ export function materializeClosedHookToolProfile({
717
719
  )
718
720
  const canonicalRepo = canonicalRepositoryRoot(repoRoot)
719
721
  const executableRecords = Object.fromEntries(
720
- ['bash', 'git', 'jq', 'node', 'python3'].map((label) => [
722
+ // perl added 2026-08-04: three canonical hooks and the hardcoded-strings lib shell out to it,
723
+ // so the pinned tool profile has to declare it like every other hook interpreter.
724
+ ['bash', 'git', 'jq', 'node', 'perl', 'python3'].map((label) => [
721
725
  label,
722
726
  resolveClosedHookExecutable({
723
727
  label,
@@ -3,8 +3,8 @@
3
3
  "schemaVersion": 1,
4
4
  "role": "template-consumer",
5
5
  "release": {
6
- "designSystem": "0.1.0-beta.108",
7
- "storybookConfig": "0.1.0-beta.108"
6
+ "designSystem": "0.1.0-beta.110",
7
+ "storybookConfig": "0.1.0-beta.110"
8
8
  },
9
9
  "upgradeTrust": {
10
10
  "schemaVersion": 1,
@@ -42,24 +42,24 @@
42
42
  "implementationSha256": "98b74118fb5b410b77ad2994aa9105fa7609c7353822f8c5213cce54d94f0af4"
43
43
  },
44
44
  "payload": {
45
- "forkCorpusLockSha256": "152b735e8828f5d108eb735c8ca9ae3cf699ee192c3159d738aac4dee30e6b47",
45
+ "forkCorpusLockSha256": "aedc7698f22b0762f672bcc7abd005e7ae3e7fa5b74ea659317743504597a634",
46
46
  "discoveryPolicySha256": "fba289aeeb5a3f11139b9589f7ab08b5e8c8799c7bdf8f0ea57ebfeb8e7c9da6",
47
47
  "providerMaterializersSha256": "732e85b01a12e7dd19469e26e4427f0e0cfe5117a78fc9f69c2f0f89702f463a",
48
- "providerLifecycleSha256": "ed952a7261b1a43d2a727b094e45ebf3e2deb7d6f0c6776b9b8a663675a5cfb8",
48
+ "providerLifecycleSha256": "12d735f89b49b79afdb9580d8dc5e79296aa7d3ca27db9f0639805492c8b94b6",
49
49
  "providerInventorySha256": "00adf1c4f6b3367055a9cc3fdfe658660771b4c9931f696e12761a95076ad92c",
50
- "executionRuntimeSha256": "b52ceb86e290913b6ecf1b2b0db330ad6ea2e0e9bfd77483aca24d69623be79b",
51
- "commonInstructionSha256": "a37c1317a52acce74ef605e3247878383bb4f99deecb98af0167d98d7cbbbe9c",
50
+ "executionRuntimeSha256": "d9377cc45308731f287d7b1711b7c38fc4e02ff8f84729bcbbf01bb52d136904",
51
+ "commonInstructionSha256": "6b9aa8747d9ddc9bd9a78de88a0c3d65f5b100ea917c4dd69be4d889b8299438",
52
52
  "providerArtifactDigests": {
53
53
  "claude": {
54
54
  "instructionSha256": "f56a44c730506258cc7e2581dbdd31bfd1e6d13d60dc8a8ee8bcc398228453fb",
55
- "hookSha256": "6398e6b20f6558392bdce1d7123815042047952be473b8fda6d2491aa1648a05",
56
- "skillsSha256": "617994510c71b6cb717dd3f06dc2dbf2ea6bd68849b5be83ca6a624ac409f697",
55
+ "hookSha256": "5c197ea686f697ea84d88a591f762dfd0ecbbf84f24a942c7d4f7d2e15f6acff",
56
+ "skillsSha256": "20482e66e435d0d05669e1a10836f08e42c88f88261f9eba6236fd0a0b78e052",
57
57
  "treeDigests": {}
58
58
  },
59
59
  "codex": {
60
- "instructionSha256": "a37c1317a52acce74ef605e3247878383bb4f99deecb98af0167d98d7cbbbe9c",
61
- "hookSha256": "cafed99be010eed1ff80459022348f8eb96f01fb3e8eadb4cb1dc10ed7d7d3e0",
62
- "skillsSha256": "a4f57953384153f393c3db3008f1967f637e0dc1d7cb149b8c47f020b62278ee",
60
+ "instructionSha256": "6b9aa8747d9ddc9bd9a78de88a0c3d65f5b100ea917c4dd69be4d889b8299438",
61
+ "hookSha256": "de8b8a9739dc9736cfe28004cbbc462dae525485202c871af9265d4dd8049740",
62
+ "skillsSha256": "6411ba088fd09f048caca23083e5ef5367887632dd3c604b11f6a1cab5060466",
63
63
  "treeDigests": {}
64
64
  },
65
65
  "generic": {
@@ -70,11 +70,11 @@
70
70
  }
71
71
  },
72
72
  "mergeSurfaceDigests": {
73
- "claude": "6398e6b20f6558392bdce1d7123815042047952be473b8fda6d2491aa1648a05"
73
+ "claude": "5c197ea686f697ea84d88a591f762dfd0ecbbf84f24a942c7d4f7d2e15f6acff"
74
74
  },
75
- "governanceCheckSha256": "393f5a016baac5a345e9c08a93c14bdb575b66570fd475a7fa86bc2e8002dd33",
75
+ "governanceCheckSha256": "1c4c4455910f1e88aebb84add9f756717395519d84913beafaa654a9f1c3126e",
76
76
  "utilityRegistrySha256": "ac430d50ee82c19eb678f725c00eec23a0e25b33ec4bad81d3bf6e2fa9fa64ff",
77
- "sharedSkillsSha256": "8b0a4563714a8b6226890f2a94b5fa28036960035e040c0224d4c127fc17efe6",
77
+ "sharedSkillsSha256": "4b74fb7b317ea53c7819f8e439f92bf5a45369840fd38892ad203b1fd11cdfd0",
78
78
  "sharedSkillCount": 11,
79
79
  "requiredScripts": {
80
80
  "audit:a11y": "node scripts/audit-consumer-a11y.mjs",
@@ -106,17 +106,17 @@
106
106
  "scripts/consumer-source-harness.mjs": "725072155d0b15b373bf6befb333cd2085dc8a38fff8538b4f4c8ea56f9c5308",
107
107
  "scripts/lib/a11y-static-server.mjs": "3ae8ca74a66ca5f155d1a1d0c97280d41f07556274c8c077705a489ebe6a7018",
108
108
  "scripts/lib/canonical-path-containment.mjs": "0244e31dc3be5015bed3efd65a5a77086a2e4676e9ac79cd0801c8cde6a74fcd",
109
- "scripts/lib/closed-tool-execution.mjs": "131255a302f1b403fe95dd6251e180d7f3ce920faf6cbfe90496b52d192d93c3",
109
+ "scripts/lib/closed-tool-execution.mjs": "eacd0cd92c5bff03e40ff4e82c02fae9bf2e6f543f157eef3d41c3ed8b7d3cb6",
110
110
  "scripts/lib/consumer-control-plane-policy.mjs": "ea8b11ddc5c964ea742ccba5e6a3bc2f215db5d165afe2cca96192812287750c",
111
111
  "scripts/lib/exact-workspace-dependencies.mjs": "3c9453e5228d7eaf1bfef3350585746e1aff1475411cb7cf8696bf9acdb048cf",
112
- "scripts/lib/governance-dependency-bootstrap.mjs": "c80d842482a3c2c16ba0d8802e1418e2560f68edcd56fe0b3125a7f6e1c9d4ec",
112
+ "scripts/lib/governance-dependency-bootstrap.mjs": "79b84cd0a7a8f262d1445503528e1251bb510517efb56550aad659c53c6b3cec",
113
113
  "scripts/lib/provider-lifecycle.mjs": "9aabb2c98efc454b8555308a34830e6590b034b10a63f76f788fbd4cf37ce7e7",
114
- "scripts/lib/verified-exact-npm-runtime.mjs": "935a669b33d5da4e15166c68f8996060d5e609a317dd68198044f830edc2a76c",
114
+ "scripts/lib/verified-exact-npm-runtime.mjs": "811ed3390a4ff6b4e818a5ce8b5c0ce34e10f48fb443b6177d6e4c8e14fa9670",
115
115
  "scripts/lib/workspace-post-create.mjs": "f3ddc6db740d5c4d48b1c6e16c55222325e4d1c16fe6644a455e7b7e5bea0a0d",
116
116
  "scripts/lib/worktree-fingerprint.mjs": "70a8bdbceade964d009f6a46c9e57a93bc9fd58c28971ea14ad1b5ffeaaf7250",
117
117
  "scripts/lint-ds-internal-imports.mjs": "86edd952fb5c3871896515440693abeb08c1d1da9f887fa073b9ddda1363f572",
118
118
  "scripts/product-template-scaffold-lock.mjs": "9134dabf11af7a2445a6a026fc23201e8aba3ef5aeb7da1d2159e92956dd32d2",
119
- "scripts/refresh-fork-launchers.mjs": "763c61964a4a971a664222e5dc0dd54564bdc9430b5a90f56021cca19d48596a",
119
+ "scripts/refresh-fork-launchers.mjs": "8d2058e9b32307a80e3a564f18abac6ea093d09e79164f468af5e1893217283f",
120
120
  "scripts/release-bom-contract.mjs": "75d736f86c7f695dc88a1982d547998d510e8050c4847abe387227b6662b87c2",
121
121
  "scripts/schemas/product-template-scaffold-lock.schema.json": "1318af59f12546b3cda059a1d94923d9a969bef4c5a476ebb46e42f8c853d19a",
122
122
  "scripts/schemas/release-bom.schema.json": "6969482588a25533dc1896be63c3c9629aced1cbeb745b99822db764682dc4a4",
@@ -66,12 +66,14 @@ const CLOSED_HOOK_EXECUTABLE_CANDIDATES = Object.freeze({
66
66
  bash: Object.freeze(['/bin/bash']),
67
67
  git: Object.freeze(['/usr/bin/git']),
68
68
  jq: Object.freeze(['/usr/bin/jq']),
69
+ perl: Object.freeze(['/usr/bin/perl']),
69
70
  python3: Object.freeze(['/usr/bin/python3']),
70
71
  }),
71
72
  linux: Object.freeze({
72
73
  bash: Object.freeze(['/usr/bin/bash', '/bin/bash']),
73
74
  git: Object.freeze(['/usr/bin/git']),
74
75
  jq: Object.freeze(['/usr/bin/jq']),
76
+ perl: Object.freeze(['/usr/bin/perl']),
75
77
  python3: Object.freeze(['/usr/bin/python3']),
76
78
  }),
77
79
  })
@@ -717,7 +719,9 @@ export function materializeClosedHookToolProfile({
717
719
  )
718
720
  const canonicalRepo = canonicalRepositoryRoot(repoRoot)
719
721
  const executableRecords = Object.fromEntries(
720
- ['bash', 'git', 'jq', 'node', 'python3'].map((label) => [
722
+ // perl added 2026-08-04: three canonical hooks and the hardcoded-strings lib shell out to it,
723
+ // so the pinned tool profile has to declare it like every other hook interpreter.
724
+ ['bash', 'git', 'jq', 'node', 'perl', 'python3'].map((label) => [
721
725
  label,
722
726
  resolveClosedHookExecutable({
723
727
  label,
@@ -233,24 +233,39 @@ function exactArray(value, expected) {
233
233
  && value.every((entry, index) => entry === expected[index])
234
234
  }
235
235
 
236
+ // Exactly the advisory set the registry serves today for the copy bundled inside npm 11.19.0.
237
+ // The disk copy is replaced with the fixed 5.0.9 by the security overlay; npm audit reads the
238
+ // LOCK, which still records the bundled 5.0.7, so the finding itself never disappears — it is
239
+ // acknowledged here in exact shape and any drift (a third advisory, a new node) fails closed.
240
+ // 2026-08-04: GHSA-rgw5-rvv9-x895 landed (<5.0.9); overlay bumped 5.0.8 → 5.0.9 the same day.
236
241
  const VERIFIED_BRACE_EXPANSION_AUDIT_PREIMAGES = Object.freeze([
237
242
  Object.freeze({
238
- source: 1124334,
239
- findingRange: '<=5.0.7',
240
- advisoryRange: '<=5.0.7',
241
- }),
242
- Object.freeze({
243
- source: 1130591,
244
- findingRange: '4.0.0 - 5.0.7',
245
- advisoryRange: '>=4.0.0 <5.0.8',
243
+ findingRange: '4.0.0 - 5.0.8',
244
+ advisories: Object.freeze([
245
+ Object.freeze({ source: 1130591, range: '>=4.0.0 <5.0.8', url: 'https://github.com/advisories/GHSA-mh99-v99m-4gvg', severity: 'high' }),
246
+ Object.freeze({ source: 1130734, range: '>=4.0.0 <5.0.9', url: 'https://github.com/advisories/GHSA-rgw5-rvv9-x895', severity: 'high' }),
247
+ ]),
246
248
  }),
247
249
  ])
248
250
 
251
+ function matchesExactAdvisorySet(finding, name, advisories) {
252
+ return Array.isArray(finding.via)
253
+ && finding.via.length === advisories.length
254
+ && advisories.every((advisory, index) => {
255
+ const via = finding.via[index]
256
+ return via?.source === advisory.source
257
+ && via?.name === name
258
+ && via?.dependency === name
259
+ && via?.range === advisory.range
260
+ && via?.url === advisory.url
261
+ && via?.severity === advisory.severity
262
+ })
263
+ }
264
+
249
265
  function matchesVerifiedBraceExpansionAuditPreimage(finding) {
250
266
  return VERIFIED_BRACE_EXPANSION_AUDIT_PREIMAGES.some((preimage) => (
251
267
  finding.range === preimage.findingRange
252
- && finding.via[0]?.source === preimage.source
253
- && finding.via[0]?.range === preimage.advisoryRange
268
+ && matchesExactAdvisorySet(finding, 'brace-expansion', preimage.advisories)
254
269
  ))
255
270
  }
256
271
 
@@ -265,17 +280,51 @@ function assertRemediatedFinding(name, finding) {
265
280
  && Array.isArray(finding.effects)
266
281
  && finding.effects.length <= 1
267
282
  && finding.effects.every((effect) => effect === 'minimatch')
268
- && Array.isArray(finding.via)
269
- && finding.via.length === 1
270
- && finding.via[0]?.name === 'brace-expansion'
271
- && finding.via[0]?.dependency === 'brace-expansion'
272
- && finding.via[0]?.url === 'https://github.com/advisories/GHSA-mh99-v99m-4gvg'
273
- && finding.via[0]?.severity === 'high'
274
283
  && matchesVerifiedBraceExpansionAuditPreimage(finding),
275
284
  'npm audit brace-expansion finding differs from the exact remediated bundled preimage',
276
285
  )
277
286
  return
278
287
  }
288
+ if (name === 'ip-address') {
289
+ // Bundled inside npm 11.19.0 and NOT yet overlaid — unlike brace-expansion/tar, the disk copy
290
+ // is still the vulnerable version. Acknowledged in exact shape only because no 11.x npm ships a
291
+ // fix and the overlay machinery has no third slot yet; the fixed 10.4.0 exists, and extending
292
+ // the overlay (or moving to npm 12) is tracked in the cloud-compat baton as the next branch.
293
+ // DoS-class parsing advisories in dev-only npm CLI internals; nothing ships to production from
294
+ // this tree. Any drift — a fourth advisory, a new node, a severity change — fails closed here.
295
+ invariant(
296
+ finding.severity === 'high'
297
+ && finding.isDirect === false
298
+ && exactArray(finding.nodes, ['node_modules/npm/node_modules/ip-address'])
299
+ && exactArray(finding.effects, [])
300
+ && finding.range === '<=10.3.0'
301
+ && matchesExactAdvisorySet(finding, 'ip-address', [
302
+ { source: 1130722, range: '<=10.3.0', url: 'https://github.com/advisories/GHSA-mwp4-54f8-5fhr', severity: 'high' },
303
+ { source: 1130723, range: '>=10.1.1 <=10.2.1', url: 'https://github.com/advisories/GHSA-4xrf-jv44-h6hh', severity: 'moderate' },
304
+ { source: 1130724, range: '>=10.1.1 <=10.2.0', url: 'https://github.com/advisories/GHSA-22jq-vg5j-6vgg', severity: 'moderate' },
305
+ ]),
306
+ 'npm audit ip-address finding differs from the acknowledged bundled preimage',
307
+ )
308
+ return
309
+ }
310
+ if (name === 'undici') {
311
+ // Same situation as ip-address: bundled in npm 11.19.0, no overlay slot yet, fixed 6.28.0
312
+ // exists. Tracked in the cloud-compat baton; exact-shape acknowledgment, drift fails closed.
313
+ invariant(
314
+ finding.severity === 'moderate'
315
+ && finding.isDirect === false
316
+ && exactArray(finding.nodes, ['node_modules/npm/node_modules/undici'])
317
+ && exactArray(finding.effects, [])
318
+ && finding.range === '<=6.27.0'
319
+ && matchesExactAdvisorySet(finding, 'undici', [
320
+ { source: 1130716, range: '<6.28.0', url: 'https://github.com/advisories/GHSA-8xcm-r25x-g524', severity: 'moderate' },
321
+ { source: 1130727, range: '<6.28.0', url: 'https://github.com/advisories/GHSA-m8rv-5g2x-5cg5', severity: 'moderate' },
322
+ { source: 1130732, range: '<6.28.0', url: 'https://github.com/advisories/GHSA-v3r7-h72x-cjcm', severity: 'moderate' },
323
+ ]),
324
+ 'npm audit undici finding differs from the acknowledged bundled preimage',
325
+ )
326
+ return
327
+ }
279
328
  if (name === 'tar') {
280
329
  invariant(
281
330
  finding.severity === 'moderate'
@@ -1201,7 +1201,7 @@ export function validateInstalledForkCorpus(projectDir) {
1201
1201
  || manifest.consumer.executionRuntime.nativeWindowsPolicy !== 'unsupported-fail-closed'
1202
1202
  || JSON.stringify(manifest.consumer.executionRuntime.supportedHostPlatforms) !== JSON.stringify(['darwin', 'linux'])
1203
1203
  || JSON.stringify(manifest.consumer.executionRuntime.windowsCompatibilityEnvironments) !== JSON.stringify(['devcontainer-linux', 'wsl2-linux'])
1204
- || JSON.stringify(manifest.consumer.executionRuntime.requiredExecutables) !== JSON.stringify(['bash', 'git', 'jq', 'node', 'python3'])
1204
+ || JSON.stringify(manifest.consumer.executionRuntime.requiredExecutables) !== JSON.stringify(['bash', 'git', 'jq', 'node', 'perl', 'python3'])
1205
1205
  || JSON.stringify(manifest.consumer.executionRuntime.requiredPathClasses) !== JSON.stringify(['plain', 'space-containing'])
1206
1206
  || bom.payload.executionRuntimeSha256 !== sha256(JSON.stringify(manifest.consumer.executionRuntime))
1207
1207
  ) throw new Error('installed consumer execution runtime contract is invalid or unbound')
@@ -40,9 +40,9 @@ const MAX_ARCHIVE_ENTRIES = 5_000
40
40
  const MAX_ARCHIVE_PATH_BYTES = 256
41
41
  const textDecoder = new TextDecoder('utf-8', { fatal: true })
42
42
  const NPM_RUNTIME_OVERLAY_ALIAS = 'npm-runtime-brace-expansion-patch'
43
- const NPM_RUNTIME_OVERLAY_SPEC = 'npm:brace-expansion@5.0.8'
43
+ const NPM_RUNTIME_OVERLAY_SPEC = 'npm:brace-expansion@5.0.9'
44
44
  const NPM_RUNTIME_OVERLAY_PACKAGE = 'brace-expansion'
45
- const NPM_RUNTIME_OVERLAY_VERSION = '5.0.8'
45
+ const NPM_RUNTIME_OVERLAY_VERSION = '5.0.9'
46
46
  const NPM_RUNTIME_OVERLAY_REPLACED_VERSION = '5.0.7'
47
47
  const NPM_RUNTIME_OVERLAY_TARGET = 'node_modules/brace-expansion'
48
48
  const NPM_RUNTIME_OVERLAY_CONSUMER = 'node_modules/minimatch'
@@ -302,7 +302,7 @@ export async function downloadCanonicalNpmTarball(url, { timeoutMs = 30_000 } =
302
302
  export async function downloadCanonicalNpmSecurityOverlayTarball(url, { timeoutMs = 30_000 } = {}) {
303
303
  return downloadCanonicalRegistryTarball(url, {
304
304
  label: 'canonical npm security overlay',
305
- pathPattern: /^\/(?:brace-expansion\/-\/brace-expansion-5\.0\.8|tar\/-\/tar-7\.5\.22)\.tgz$/,
305
+ pathPattern: /^\/(?:brace-expansion\/-\/brace-expansion-5\.0\.9|tar\/-\/tar-7\.5\.22)\.tgz$/,
306
306
  timeoutMs,
307
307
  })
308
308
  }