@qijenchen/design-system 0.1.0-beta.13 → 0.1.0-beta.130
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/AGENTS.md +8 -0
- package/CLAUDE.md +3 -199
- package/README.md +6 -4
- package/cli-init.mjs +30 -2
- package/dist/components/Accordion/accordion.d.ts +3 -3
- package/dist/components/Accordion/accordion.d.ts.map +1 -1
- package/dist/components/Accordion/accordion.js +6 -3
- package/dist/components/Accordion/accordion.js.map +1 -1
- package/dist/components/Accordion/index.d.ts +2 -0
- package/dist/components/Accordion/index.d.ts.map +1 -0
- package/dist/components/Accordion/index.js +9 -0
- package/dist/components/Accordion/index.js.map +1 -0
- package/dist/components/AccountMenu/account-menu.d.ts +93 -0
- package/dist/components/AccountMenu/account-menu.d.ts.map +1 -0
- package/dist/components/AccountMenu/account-menu.js +73 -0
- package/dist/components/AccountMenu/account-menu.js.map +1 -0
- package/dist/components/AccountMenu/index.d.ts +2 -0
- package/dist/components/AccountMenu/index.d.ts.map +1 -0
- package/dist/components/AccountMenu/index.js +6 -0
- package/dist/components/AccountMenu/index.js.map +1 -0
- package/dist/components/AgentPanel/agent-fab.d.ts +24 -0
- package/dist/components/AgentPanel/agent-fab.d.ts.map +1 -0
- package/dist/components/AgentPanel/agent-logo.d.ts +40 -0
- package/dist/components/AgentPanel/agent-logo.d.ts.map +1 -0
- package/dist/components/AgentPanel/agent-logo.js +344 -0
- package/dist/components/AgentPanel/agent-logo.js.map +1 -0
- package/dist/components/AgentPanel/agent-panel.d.ts +148 -0
- package/dist/components/AgentPanel/agent-panel.d.ts.map +1 -0
- package/dist/components/AgentPanel/agent-panel.js +670 -0
- package/dist/components/AgentPanel/agent-panel.js.map +1 -0
- package/dist/components/AgentPanel/index.d.ts +2 -0
- package/dist/components/AgentPanel/index.d.ts.map +1 -0
- package/dist/components/AgentPanel/index.js +13 -0
- package/dist/components/AgentPanel/index.js.map +1 -0
- package/dist/components/Alert/alert.d.ts +7 -2
- package/dist/components/Alert/alert.d.ts.map +1 -1
- package/dist/components/Alert/alert.js +12 -8
- package/dist/components/Alert/alert.js.map +1 -1
- package/dist/components/Alert/index.d.ts +2 -0
- package/dist/components/Alert/index.d.ts.map +1 -0
- package/dist/components/Alert/index.js +7 -0
- package/dist/components/Alert/index.js.map +1 -0
- package/dist/components/AppShell/_demo-helpers.d.ts +3 -1
- package/dist/components/AppShell/_demo-helpers.d.ts.map +1 -1
- package/dist/components/AppShell/app-shell.d.ts +19 -4
- package/dist/components/AppShell/app-shell.d.ts.map +1 -1
- package/dist/components/AppShell/app-shell.js +48 -11
- package/dist/components/AppShell/app-shell.js.map +1 -1
- package/dist/components/AppShell/index.d.ts +2 -0
- package/dist/components/AppShell/index.d.ts.map +1 -0
- package/dist/components/AppShell/index.js +8 -0
- package/dist/components/AppShell/index.js.map +1 -0
- package/dist/components/AspectRatio/aspect-ratio.d.ts +3 -2
- package/dist/components/AspectRatio/aspect-ratio.d.ts.map +1 -1
- package/dist/components/AspectRatio/aspect-ratio.js +4 -2
- package/dist/components/AspectRatio/aspect-ratio.js.map +1 -1
- package/dist/components/AspectRatio/index.d.ts +2 -0
- package/dist/components/AspectRatio/index.d.ts.map +1 -0
- package/dist/components/AspectRatio/index.js +6 -0
- package/dist/components/AspectRatio/index.js.map +1 -0
- package/dist/components/Avatar/avatar.d.ts +14 -7
- package/dist/components/Avatar/avatar.d.ts.map +1 -1
- package/dist/components/Avatar/avatar.js +24 -30
- package/dist/components/Avatar/avatar.js.map +1 -1
- package/dist/components/Avatar/index.d.ts +2 -0
- package/dist/components/Avatar/index.d.ts.map +1 -0
- package/dist/components/Avatar/index.js +6 -0
- package/dist/components/Avatar/index.js.map +1 -0
- package/dist/components/Badge/badge.d.ts +21 -9
- package/dist/components/Badge/badge.d.ts.map +1 -1
- package/dist/components/Badge/badge.js +9 -6
- package/dist/components/Badge/badge.js.map +1 -1
- package/dist/components/Badge/index.d.ts +2 -0
- package/dist/components/Badge/index.d.ts.map +1 -0
- package/dist/components/Badge/index.js +7 -0
- package/dist/components/Badge/index.js.map +1 -0
- package/dist/components/Breadcrumb/breadcrumb.d.ts +16 -3
- package/dist/components/Breadcrumb/breadcrumb.d.ts.map +1 -1
- package/dist/components/Breadcrumb/breadcrumb.js +21 -53
- package/dist/components/Breadcrumb/breadcrumb.js.map +1 -1
- package/dist/components/Breadcrumb/index.d.ts +2 -0
- package/dist/components/Breadcrumb/index.d.ts.map +1 -0
- package/dist/components/Breadcrumb/index.js +12 -0
- package/dist/components/Breadcrumb/index.js.map +1 -0
- package/dist/components/BulkActionBar/bulk-action-bar.d.ts +7 -4
- package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.js +8 -8
- package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
- package/dist/components/BulkActionBar/index.d.ts +2 -0
- package/dist/components/BulkActionBar/index.d.ts.map +1 -0
- package/dist/components/BulkActionBar/index.js +7 -0
- package/dist/components/BulkActionBar/index.js.map +1 -0
- package/dist/components/Button/button-group.d.ts +31 -5
- package/dist/components/Button/button-group.d.ts.map +1 -1
- package/dist/components/Button/button-group.js +31 -14
- package/dist/components/Button/button-group.js.map +1 -1
- package/dist/components/Button/button.d.ts +15 -14
- package/dist/components/Button/button.d.ts.map +1 -1
- package/dist/components/Button/button.js +94 -52
- package/dist/components/Button/button.js.map +1 -1
- package/dist/components/Button/index.d.ts +3 -0
- package/dist/components/Button/index.d.ts.map +1 -0
- package/dist/components/Button/index.js +11 -0
- package/dist/components/Button/index.js.map +1 -0
- package/dist/components/Calendar/calendar.d.ts +22 -19
- package/dist/components/Calendar/calendar.d.ts.map +1 -1
- package/dist/components/Calendar/calendar.js +180 -135
- package/dist/components/Calendar/calendar.js.map +1 -1
- package/dist/components/Calendar/index.d.ts +2 -0
- package/dist/components/Calendar/index.d.ts.map +1 -0
- package/dist/components/Calendar/index.js +6 -0
- package/dist/components/Calendar/index.js.map +1 -0
- package/dist/components/Carousel/carousel.d.ts +15 -9
- package/dist/components/Carousel/carousel.d.ts.map +1 -1
- package/dist/components/Carousel/carousel.js +91 -50
- package/dist/components/Carousel/carousel.js.map +1 -1
- package/dist/components/Carousel/index.d.ts +2 -0
- package/dist/components/Carousel/index.d.ts.map +1 -0
- package/dist/components/Carousel/index.js +11 -0
- package/dist/components/Carousel/index.js.map +1 -0
- package/dist/components/Chart/chart.d.ts +2 -2
- package/dist/components/Chart/chart.d.ts.map +1 -1
- package/dist/components/Chart/chart.js +5 -2
- package/dist/components/Chart/chart.js.map +1 -1
- package/dist/components/Chart/index.d.ts +2 -0
- package/dist/components/Chart/index.d.ts.map +1 -0
- package/dist/components/Chart/index.js +11 -0
- package/dist/components/Chart/index.js.map +1 -0
- package/dist/components/Checkbox/checkbox-group.d.ts +2 -1
- package/dist/components/Checkbox/checkbox-group.d.ts.map +1 -1
- package/dist/components/Checkbox/checkbox-group.js +21 -14
- package/dist/components/Checkbox/checkbox-group.js.map +1 -1
- package/dist/components/Checkbox/checkbox.d.ts +20 -11
- package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
- package/dist/components/Checkbox/checkbox.js +117 -18
- package/dist/components/Checkbox/checkbox.js.map +1 -1
- package/dist/components/Checkbox/index.d.ts +3 -0
- package/dist/components/Checkbox/index.d.ts.map +1 -0
- package/dist/components/Checkbox/index.js +10 -0
- package/dist/components/Checkbox/index.js.map +1 -0
- package/dist/components/Chip/chip.d.ts +30 -11
- package/dist/components/Chip/chip.d.ts.map +1 -1
- package/dist/components/Chip/chip.js +83 -33
- package/dist/components/Chip/chip.js.map +1 -1
- package/dist/components/Chip/index.d.ts +2 -0
- package/dist/components/Chip/index.d.ts.map +1 -0
- package/dist/components/Chip/index.js +8 -0
- package/dist/components/Chip/index.js.map +1 -0
- package/dist/components/CircularProgress/circular-progress.d.ts +2 -3
- package/dist/components/CircularProgress/circular-progress.d.ts.map +1 -1
- package/dist/components/CircularProgress/circular-progress.js +5 -4
- package/dist/components/CircularProgress/circular-progress.js.map +1 -1
- package/dist/components/CircularProgress/index.d.ts +2 -0
- package/dist/components/CircularProgress/index.d.ts.map +1 -0
- package/dist/components/CircularProgress/index.js +6 -0
- package/dist/components/CircularProgress/index.js.map +1 -0
- package/dist/components/Coachmark/coachmark-story-helpers.d.ts +22 -0
- package/dist/components/Coachmark/coachmark-story-helpers.d.ts.map +1 -0
- package/dist/components/Coachmark/coachmark.d.ts +15 -6
- package/dist/components/Coachmark/coachmark.d.ts.map +1 -1
- package/dist/components/Coachmark/coachmark.js +12 -4
- package/dist/components/Coachmark/coachmark.js.map +1 -1
- package/dist/components/Coachmark/index.d.ts +2 -0
- package/dist/components/Coachmark/index.d.ts.map +1 -0
- package/dist/components/Coachmark/index.js +6 -0
- package/dist/components/Coachmark/index.js.map +1 -0
- package/dist/components/Combobox/combobox.d.ts +39 -15
- package/dist/components/Combobox/combobox.d.ts.map +1 -1
- package/dist/components/Combobox/combobox.js +169 -65
- package/dist/components/Combobox/combobox.js.map +1 -1
- package/dist/components/Combobox/index.d.ts +2 -0
- package/dist/components/Combobox/index.d.ts.map +1 -0
- package/dist/components/Combobox/index.js +6 -0
- package/dist/components/Combobox/index.js.map +1 -0
- package/dist/components/Command/command.d.ts +7 -3
- package/dist/components/Command/command.d.ts.map +1 -1
- package/dist/components/Command/command.js +23 -10
- package/dist/components/Command/command.js.map +1 -1
- package/dist/components/Command/index.d.ts +2 -0
- package/dist/components/Command/index.d.ts.map +1 -0
- package/dist/components/Command/index.js +14 -0
- package/dist/components/Command/index.js.map +1 -0
- package/dist/components/DataTable/active-editor-controller.d.ts +4 -3
- package/dist/components/DataTable/active-editor-controller.d.ts.map +1 -1
- package/dist/components/DataTable/cell-registry.d.ts +5 -5
- package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
- package/dist/components/DataTable/cell-registry.js +89 -95
- package/dist/components/DataTable/cell-registry.js.map +1 -1
- package/dist/components/DataTable/column-types.d.ts +11 -21
- package/dist/components/DataTable/column-types.d.ts.map +1 -1
- package/dist/components/DataTable/column-types.js.map +1 -1
- package/dist/components/DataTable/data-table-column-visibility-panel.d.ts +4 -3
- package/dist/components/DataTable/data-table-column-visibility-panel.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-column-visibility-panel.js +193 -0
- package/dist/components/DataTable/data-table-column-visibility-panel.js.map +1 -0
- package/dist/components/DataTable/data-table-filter-group.d.ts +51 -0
- package/dist/components/DataTable/data-table-filter-group.d.ts.map +1 -0
- package/dist/components/DataTable/data-table-filter-group.js +159 -0
- package/dist/components/DataTable/data-table-filter-group.js.map +1 -0
- package/dist/components/DataTable/data-table-filter-labels.d.ts +44 -0
- package/dist/components/DataTable/data-table-filter-labels.d.ts.map +1 -0
- package/dist/components/DataTable/data-table-filter-labels.js +55 -0
- package/dist/components/DataTable/data-table-filter-labels.js.map +1 -0
- package/dist/components/DataTable/data-table-filter-panel.d.ts +12 -3
- package/dist/components/DataTable/data-table-filter-panel.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-filter-panel.js +316 -0
- package/dist/components/DataTable/data-table-filter-panel.js.map +1 -0
- package/dist/components/DataTable/data-table-filter-value-picker.d.ts +32 -0
- package/dist/components/DataTable/data-table-filter-value-picker.d.ts.map +1 -0
- package/dist/components/DataTable/data-table-filter-value-picker.js +205 -0
- package/dist/components/DataTable/data-table-filter-value-picker.js.map +1 -0
- package/dist/components/DataTable/data-table-interaction-layer.d.ts +17 -20
- package/dist/components/DataTable/data-table-interaction-layer.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-interaction-layer.js +1 -3
- package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -1
- package/dist/components/DataTable/data-table-sort-manager.d.ts +5 -5
- package/dist/components/DataTable/data-table-sort-manager.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-sort-manager.js +150 -0
- package/dist/components/DataTable/data-table-sort-manager.js.map +1 -0
- package/dist/components/DataTable/data-table.d.ts +66 -15
- package/dist/components/DataTable/data-table.d.ts.map +1 -1
- package/dist/components/DataTable/data-table.js +367 -185
- package/dist/components/DataTable/data-table.js.map +1 -1
- package/dist/components/DataTable/filter-operators.d.ts +1 -1
- package/dist/components/DataTable/filter-operators.js +140 -0
- package/dist/components/DataTable/filter-operators.js.map +1 -0
- package/dist/components/DataTable/filter-tree.d.ts +6 -1
- package/dist/components/DataTable/filter-tree.d.ts.map +1 -1
- package/dist/components/DataTable/filter-tree.js +318 -0
- package/dist/components/DataTable/filter-tree.js.map +1 -0
- package/dist/components/DataTable/index.d.ts +5 -0
- package/dist/components/DataTable/index.d.ts.map +1 -0
- package/dist/components/DataTable/index.js +21 -0
- package/dist/components/DataTable/index.js.map +1 -0
- package/dist/components/DataTable/lib/column-meta.js +25 -0
- package/dist/components/DataTable/lib/column-meta.js.map +1 -0
- package/dist/components/DateGrid/date-grid.d.ts +23 -19
- package/dist/components/DateGrid/date-grid.d.ts.map +1 -1
- package/dist/components/DateGrid/date-grid.js +60 -12
- package/dist/components/DateGrid/date-grid.js.map +1 -1
- package/dist/components/DateGrid/index.d.ts +2 -0
- package/dist/components/DateGrid/index.d.ts.map +1 -0
- package/dist/components/DateGrid/index.js +6 -0
- package/dist/components/DateGrid/index.js.map +1 -0
- package/dist/components/DatePicker/date-picker.d.ts +12 -12
- package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
- package/dist/components/DatePicker/date-picker.js +418 -309
- package/dist/components/DatePicker/date-picker.js.map +1 -1
- package/dist/components/DatePicker/index.d.ts +2 -0
- package/dist/components/DatePicker/index.d.ts.map +1 -0
- package/dist/components/DatePicker/index.js +8 -0
- package/dist/components/DatePicker/index.js.map +1 -0
- package/dist/components/DescriptionList/description-list.d.ts +6 -6
- package/dist/components/DescriptionList/description-list.d.ts.map +1 -1
- package/dist/components/DescriptionList/description-list.js +11 -10
- package/dist/components/DescriptionList/description-list.js.map +1 -1
- package/dist/components/DescriptionList/index.d.ts +2 -0
- package/dist/components/DescriptionList/index.d.ts.map +1 -0
- package/dist/components/DescriptionList/index.js +7 -0
- package/dist/components/DescriptionList/index.js.map +1 -0
- package/dist/components/Dialog/dialog.d.ts +22 -5
- package/dist/components/Dialog/dialog.d.ts.map +1 -1
- package/dist/components/Dialog/dialog.js +44 -23
- package/dist/components/Dialog/dialog.js.map +1 -1
- package/dist/components/Dialog/index.d.ts +2 -0
- package/dist/components/Dialog/index.d.ts.map +1 -0
- package/dist/components/Dialog/index.js +16 -0
- package/dist/components/Dialog/index.js.map +1 -0
- package/dist/components/DropdownMenu/dropdown-menu.d.ts +19 -10
- package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.js +79 -28
- package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
- package/dist/components/DropdownMenu/index.d.ts +2 -0
- package/dist/components/DropdownMenu/index.d.ts.map +1 -0
- package/dist/components/DropdownMenu/index.js +21 -0
- package/dist/components/DropdownMenu/index.js.map +1 -0
- package/dist/components/Empty/empty.d.ts +14 -6
- package/dist/components/Empty/empty.d.ts.map +1 -1
- package/dist/components/Empty/empty.js +7 -7
- package/dist/components/Empty/empty.js.map +1 -1
- package/dist/components/Empty/index.d.ts +2 -0
- package/dist/components/Empty/index.d.ts.map +1 -0
- package/dist/components/Empty/index.js +6 -0
- package/dist/components/Empty/index.js.map +1 -0
- package/dist/components/Field/field-context.d.ts +111 -4
- package/dist/components/Field/field-context.d.ts.map +1 -1
- package/dist/components/Field/field-context.js +70 -0
- package/dist/components/Field/field-context.js.map +1 -1
- package/dist/components/Field/field-edit-keys.d.ts +21 -0
- package/dist/components/Field/field-edit-keys.d.ts.map +1 -0
- package/dist/components/Field/field-edit-keys.js +19 -0
- package/dist/components/Field/field-edit-keys.js.map +1 -0
- package/dist/components/Field/field-types.d.ts +22 -3
- package/dist/components/Field/field-types.d.ts.map +1 -1
- package/dist/components/Field/field-types.js.map +1 -1
- package/dist/components/Field/field-wrapper.d.ts +24 -6
- package/dist/components/Field/field-wrapper.d.ts.map +1 -1
- package/dist/components/Field/field-wrapper.js +98 -74
- package/dist/components/Field/field-wrapper.js.map +1 -1
- package/dist/components/Field/field.d.ts +7 -4
- package/dist/components/Field/field.d.ts.map +1 -1
- package/dist/components/Field/field.js +39 -6
- package/dist/components/Field/field.js.map +1 -1
- package/dist/components/Field/index.d.ts +3 -0
- package/dist/components/Field/index.d.ts.map +1 -0
- package/dist/components/Field/index.js +14 -0
- package/dist/components/Field/index.js.map +1 -0
- package/dist/components/Field/use-form-validation.d.ts +92 -0
- package/dist/components/Field/use-form-validation.d.ts.map +1 -0
- package/dist/components/Field/use-form-validation.js +137 -0
- package/dist/components/Field/use-form-validation.js.map +1 -0
- package/dist/components/FieldControlGroup/field-control-group.d.ts +8 -11
- package/dist/components/FieldControlGroup/field-control-group.d.ts.map +1 -1
- package/dist/components/FieldControlGroup/field-control-group.js +2 -2
- package/dist/components/FieldControlGroup/field-control-group.js.map +1 -1
- package/dist/components/FieldControlGroup/index.d.ts +2 -0
- package/dist/components/FieldControlGroup/index.d.ts.map +1 -0
- package/dist/components/FieldControlGroup/index.js +6 -0
- package/dist/components/FieldControlGroup/index.js.map +1 -0
- package/dist/components/FileItem/file-item.d.ts +14 -2
- package/dist/components/FileItem/file-item.d.ts.map +1 -1
- package/dist/components/FileItem/file-item.js +45 -16
- package/dist/components/FileItem/file-item.js.map +1 -1
- package/dist/components/FileItem/index.d.ts +2 -0
- package/dist/components/FileItem/index.d.ts.map +1 -0
- package/dist/components/FileItem/index.js +6 -0
- package/dist/components/FileItem/index.js.map +1 -0
- package/dist/components/FileUpload/file-upload.d.ts +39 -19
- package/dist/components/FileUpload/file-upload.d.ts.map +1 -1
- package/dist/components/FileUpload/file-upload.js +102 -32
- package/dist/components/FileUpload/file-upload.js.map +1 -1
- package/dist/components/FileUpload/index.d.ts +2 -0
- package/dist/components/FileUpload/index.d.ts.map +1 -0
- package/dist/components/FileUpload/index.js +6 -0
- package/dist/components/FileUpload/index.js.map +1 -0
- package/dist/components/FileViewer/can-render-image.d.ts +6 -0
- package/dist/components/FileViewer/can-render-image.d.ts.map +1 -0
- package/dist/components/FileViewer/can-render-image.js +11 -0
- package/dist/components/FileViewer/can-render-image.js.map +1 -0
- package/dist/components/FileViewer/file-viewer.d.ts +4 -4
- package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
- package/dist/components/FileViewer/file-viewer.js +88 -50
- package/dist/components/FileViewer/file-viewer.js.map +1 -1
- package/dist/components/FileViewer/image-renderer.d.ts +0 -5
- package/dist/components/FileViewer/image-renderer.d.ts.map +1 -1
- package/dist/components/FileViewer/image-renderer.js +23 -10
- package/dist/components/FileViewer/image-renderer.js.map +1 -1
- package/dist/components/FileViewer/index.d.ts +2 -0
- package/dist/components/FileViewer/index.d.ts.map +1 -0
- package/dist/components/FileViewer/index.js +7 -0
- package/dist/components/FileViewer/index.js.map +1 -0
- package/dist/components/HoverCard/hover-card.d.ts +9 -5
- package/dist/components/HoverCard/hover-card.d.ts.map +1 -1
- package/dist/components/HoverCard/hover-card.js +12 -5
- package/dist/components/HoverCard/hover-card.js.map +1 -1
- package/dist/components/HoverCard/index.d.ts +2 -0
- package/dist/components/HoverCard/index.d.ts.map +1 -0
- package/dist/components/HoverCard/index.js +8 -0
- package/dist/components/HoverCard/index.js.map +1 -0
- package/dist/components/InlineEdit/index.d.ts +2 -0
- package/dist/components/InlineEdit/index.d.ts.map +1 -0
- package/dist/components/InlineEdit/index.js +6 -0
- package/dist/components/InlineEdit/index.js.map +1 -0
- package/dist/components/InlineEdit/inline-edit.d.ts +86 -0
- package/dist/components/InlineEdit/inline-edit.d.ts.map +1 -0
- package/dist/components/InlineEdit/inline-edit.js +254 -0
- package/dist/components/InlineEdit/inline-edit.js.map +1 -0
- package/dist/components/Input/index.d.ts +2 -0
- package/dist/components/Input/index.d.ts.map +1 -0
- package/dist/components/Input/index.js +6 -0
- package/dist/components/Input/index.js.map +1 -0
- package/dist/components/Input/input.d.ts +31 -10
- package/dist/components/Input/input.d.ts.map +1 -1
- package/dist/components/Input/input.js +67 -30
- package/dist/components/Input/input.js.map +1 -1
- package/dist/components/LinkInput/index.d.ts +2 -0
- package/dist/components/LinkInput/index.d.ts.map +1 -0
- package/dist/components/LinkInput/index.js +6 -0
- package/dist/components/LinkInput/index.js.map +1 -0
- package/dist/components/LinkInput/link-input.d.ts +9 -9
- package/dist/components/LinkInput/link-input.d.ts.map +1 -1
- package/dist/components/LinkInput/link-input.js +40 -37
- package/dist/components/LinkInput/link-input.js.map +1 -1
- package/dist/components/Menu/index.d.ts +2 -0
- package/dist/components/Menu/index.d.ts.map +1 -0
- package/dist/components/Menu/index.js +9 -0
- package/dist/components/Menu/index.js.map +1 -0
- package/dist/components/Menu/menu-item.d.ts +20 -4
- package/dist/components/Menu/menu-item.d.ts.map +1 -1
- package/dist/components/Menu/menu-item.js +37 -9
- package/dist/components/Menu/menu-item.js.map +1 -1
- package/dist/components/Notice/index.d.ts +2 -0
- package/dist/components/Notice/index.d.ts.map +1 -0
- package/dist/components/Notice/index.js +8 -0
- package/dist/components/Notice/index.js.map +1 -0
- package/dist/components/Notice/notice.d.ts +35 -7
- package/dist/components/Notice/notice.d.ts.map +1 -1
- package/dist/components/Notice/notice.js +34 -10
- package/dist/components/Notice/notice.js.map +1 -1
- package/dist/components/NumberInput/index.d.ts +2 -0
- package/dist/components/NumberInput/index.d.ts.map +1 -0
- package/dist/components/NumberInput/index.js +7 -0
- package/dist/components/NumberInput/index.js.map +1 -0
- package/dist/components/NumberInput/number-input.d.ts +5 -5
- package/dist/components/NumberInput/number-input.d.ts.map +1 -1
- package/dist/components/NumberInput/number-input.js +40 -18
- package/dist/components/NumberInput/number-input.js.map +1 -1
- package/dist/components/OverflowIndicator/index.d.ts +2 -0
- package/dist/components/OverflowIndicator/index.d.ts.map +1 -0
- package/dist/components/OverflowIndicator/index.js +6 -0
- package/dist/components/OverflowIndicator/index.js.map +1 -0
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts +6 -2
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts.map +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.js +11 -7
- package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
- package/dist/components/Pagination/index.d.ts +2 -0
- package/dist/components/Pagination/index.d.ts.map +1 -0
- package/dist/components/Pagination/index.js +6 -0
- package/dist/components/Pagination/index.js.map +1 -0
- package/dist/components/Pagination/pagination.d.ts +49 -0
- package/dist/components/Pagination/pagination.d.ts.map +1 -0
- package/dist/components/Pagination/pagination.js +182 -0
- package/dist/components/Pagination/pagination.js.map +1 -0
- package/dist/components/PeoplePicker/avatar-stack-overflow.d.ts +1 -1
- package/dist/components/PeoplePicker/avatar-stack-overflow.js.map +1 -1
- package/dist/components/PeoplePicker/index.d.ts +2 -0
- package/dist/components/PeoplePicker/index.d.ts.map +1 -0
- package/dist/components/PeoplePicker/index.js +15 -0
- package/dist/components/PeoplePicker/index.js.map +1 -0
- package/dist/components/PeoplePicker/people-picker-helpers.d.ts.map +1 -1
- package/dist/components/PeoplePicker/people-picker-helpers.js +2 -2
- package/dist/components/PeoplePicker/people-picker-helpers.js.map +1 -1
- package/dist/components/PeoplePicker/people-picker.d.ts +26 -10
- package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
- package/dist/components/PeoplePicker/people-picker.js +97 -65
- package/dist/components/PeoplePicker/people-picker.js.map +1 -1
- package/dist/components/PeoplePicker/person-display.d.ts +19 -11
- package/dist/components/PeoplePicker/person-display.d.ts.map +1 -1
- package/dist/components/PeoplePicker/person-display.js +39 -23
- package/dist/components/PeoplePicker/person-display.js.map +1 -1
- package/dist/components/Popover/index.d.ts +2 -0
- package/dist/components/Popover/index.d.ts.map +1 -0
- package/dist/components/Popover/index.js +14 -0
- package/dist/components/Popover/index.js.map +1 -0
- package/dist/components/Popover/popover.d.ts +2 -2
- package/dist/components/Popover/popover.d.ts.map +1 -1
- package/dist/components/Popover/popover.js +72 -42
- package/dist/components/Popover/popover.js.map +1 -1
- package/dist/components/ProfileCard/index.d.ts +2 -0
- package/dist/components/ProfileCard/index.d.ts.map +1 -0
- package/dist/components/ProfileCard/index.js +8 -0
- package/dist/components/ProfileCard/index.js.map +1 -0
- package/dist/components/{NameCard/name-card.d.ts → ProfileCard/profile-card.d.ts} +34 -20
- package/dist/components/ProfileCard/profile-card.d.ts.map +1 -0
- package/dist/components/{NameCard/name-card.js → ProfileCard/profile-card.js} +27 -16
- package/dist/components/ProfileCard/profile-card.js.map +1 -0
- package/dist/components/ProgressBar/index.d.ts +2 -0
- package/dist/components/ProgressBar/index.d.ts.map +1 -0
- package/dist/components/ProgressBar/index.js +6 -0
- package/dist/components/ProgressBar/index.js.map +1 -0
- package/dist/components/ProgressBar/progress-bar.d.ts +2 -14
- package/dist/components/ProgressBar/progress-bar.d.ts.map +1 -1
- package/dist/components/ProgressBar/progress-bar.js +5 -9
- package/dist/components/ProgressBar/progress-bar.js.map +1 -1
- package/dist/components/RadioGroup/index.d.ts +2 -0
- package/dist/components/RadioGroup/index.d.ts.map +1 -0
- package/dist/components/RadioGroup/index.js +8 -0
- package/dist/components/RadioGroup/index.js.map +1 -0
- package/dist/components/RadioGroup/radio-group.d.ts +18 -12
- package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
- package/dist/components/RadioGroup/radio-group.js +131 -40
- package/dist/components/RadioGroup/radio-group.js.map +1 -1
- package/dist/components/Rating/index.d.ts +2 -0
- package/dist/components/Rating/index.d.ts.map +1 -0
- package/dist/components/Rating/index.js +6 -0
- package/dist/components/Rating/index.js.map +1 -0
- package/dist/components/Rating/rating.d.ts +6 -2
- package/dist/components/Rating/rating.d.ts.map +1 -1
- package/dist/components/Rating/rating.js +17 -10
- package/dist/components/Rating/rating.js.map +1 -1
- package/dist/components/ScrollArea/index.d.ts +2 -0
- package/dist/components/ScrollArea/index.d.ts.map +1 -0
- package/dist/components/ScrollArea/index.js +7 -0
- package/dist/components/ScrollArea/index.js.map +1 -0
- package/dist/components/ScrollArea/scroll-area.d.ts +16 -4
- package/dist/components/ScrollArea/scroll-area.d.ts.map +1 -1
- package/dist/components/ScrollArea/scroll-area.js +9 -6
- package/dist/components/ScrollArea/scroll-area.js.map +1 -1
- package/dist/components/SegmentedControl/index.d.ts +2 -0
- package/dist/components/SegmentedControl/index.d.ts.map +1 -0
- package/dist/components/SegmentedControl/index.js +9 -0
- package/dist/components/SegmentedControl/index.js.map +1 -0
- package/dist/components/SegmentedControl/segmented-control.d.ts +8 -7
- package/dist/components/SegmentedControl/segmented-control.d.ts.map +1 -1
- package/dist/components/SegmentedControl/segmented-control.js +31 -20
- package/dist/components/SegmentedControl/segmented-control.js.map +1 -1
- package/dist/components/Select/index.d.ts +2 -0
- package/dist/components/Select/index.d.ts.map +1 -0
- package/dist/components/Select/index.js +6 -0
- package/dist/components/Select/index.js.map +1 -0
- package/dist/components/Select/select.d.ts +70 -13
- package/dist/components/Select/select.d.ts.map +1 -1
- package/dist/components/Select/select.js +271 -83
- package/dist/components/Select/select.js.map +1 -1
- package/dist/components/SelectMenu/index.d.ts +2 -0
- package/dist/components/SelectMenu/index.d.ts.map +1 -0
- package/dist/components/SelectMenu/index.js +8 -0
- package/dist/components/SelectMenu/index.js.map +1 -0
- package/dist/components/SelectMenu/select-menu.d.ts +61 -10
- package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
- package/dist/components/SelectMenu/select-menu.js +216 -93
- package/dist/components/SelectMenu/select-menu.js.map +1 -1
- package/dist/components/SelectionControl/boolean-value.d.ts +11 -0
- package/dist/components/SelectionControl/boolean-value.d.ts.map +1 -0
- package/dist/components/SelectionControl/boolean-value.js +23 -0
- package/dist/components/SelectionControl/boolean-value.js.map +1 -0
- package/dist/components/SelectionControl/index.d.ts +3 -0
- package/dist/components/SelectionControl/index.d.ts.map +1 -0
- package/dist/components/SelectionControl/index.js +9 -0
- package/dist/components/SelectionControl/index.js.map +1 -0
- package/dist/components/SelectionControl/selection-item.d.ts +5 -1
- package/dist/components/SelectionControl/selection-item.d.ts.map +1 -1
- package/dist/components/SelectionControl/selection-item.js +3 -1
- package/dist/components/SelectionControl/selection-item.js.map +1 -1
- package/dist/components/Separator/index.d.ts +2 -0
- package/dist/components/Separator/index.d.ts.map +1 -0
- package/dist/components/Separator/index.js +6 -0
- package/dist/components/Separator/index.js.map +1 -0
- package/dist/components/Separator/separator.d.ts +2 -2
- package/dist/components/Separator/separator.js +4 -3
- package/dist/components/Separator/separator.js.map +1 -1
- package/dist/components/Sheet/index.d.ts +2 -0
- package/dist/components/Sheet/index.d.ts.map +1 -0
- package/dist/components/Sheet/index.js +18 -0
- package/dist/components/Sheet/index.js.map +1 -0
- package/dist/components/Sheet/sheet.d.ts +15 -8
- package/dist/components/Sheet/sheet.d.ts.map +1 -1
- package/dist/components/Sheet/sheet.js +9 -5
- package/dist/components/Sheet/sheet.js.map +1 -1
- package/dist/components/Sidebar/index.d.ts +2 -0
- package/dist/components/Sidebar/index.d.ts.map +1 -0
- package/dist/components/Sidebar/index.js +24 -0
- package/dist/components/Sidebar/index.js.map +1 -0
- package/dist/components/Sidebar/sidebar.d.ts +17 -11
- package/dist/components/Sidebar/sidebar.d.ts.map +1 -1
- package/dist/components/Sidebar/sidebar.js +78 -35
- package/dist/components/Sidebar/sidebar.js.map +1 -1
- package/dist/components/Skeleton/index.d.ts +2 -0
- package/dist/components/Skeleton/index.d.ts.map +1 -0
- package/dist/components/Skeleton/index.js +6 -0
- package/dist/components/Skeleton/index.js.map +1 -0
- package/dist/components/Skeleton/skeleton.d.ts +2 -2
- package/dist/components/Skeleton/skeleton.js +4 -3
- package/dist/components/Skeleton/skeleton.js.map +1 -1
- package/dist/components/Slider/index.d.ts +2 -0
- package/dist/components/Slider/index.d.ts.map +1 -0
- package/dist/components/Slider/index.js +7 -0
- package/dist/components/Slider/index.js.map +1 -0
- package/dist/components/Slider/slider.d.ts +7 -7
- package/dist/components/Slider/slider.d.ts.map +1 -1
- package/dist/components/Slider/slider.js +39 -17
- package/dist/components/Slider/slider.js.map +1 -1
- package/dist/components/Steps/index.d.ts +2 -0
- package/dist/components/Steps/index.d.ts.map +1 -0
- package/dist/components/Steps/index.js +12 -0
- package/dist/components/Steps/index.js.map +1 -0
- package/dist/components/Steps/steps.d.ts +8 -6
- package/dist/components/Steps/steps.d.ts.map +1 -1
- package/dist/components/Steps/steps.js +74 -32
- package/dist/components/Steps/steps.js.map +1 -1
- package/dist/components/Switch/index.d.ts +2 -0
- package/dist/components/Switch/index.d.ts.map +1 -0
- package/dist/components/Switch/index.js +7 -0
- package/dist/components/Switch/index.js.map +1 -0
- package/dist/components/Switch/switch.d.ts +20 -18
- package/dist/components/Switch/switch.d.ts.map +1 -1
- package/dist/components/Switch/switch.js +133 -26
- package/dist/components/Switch/switch.js.map +1 -1
- package/dist/components/Tabs/index.d.ts +2 -0
- package/dist/components/Tabs/index.d.ts.map +1 -0
- package/dist/components/Tabs/index.js +10 -0
- package/dist/components/Tabs/index.js.map +1 -0
- package/dist/components/Tabs/tabs-inline-action-geometry.d.ts +31 -0
- package/dist/components/Tabs/tabs-inline-action-geometry.d.ts.map +1 -0
- package/dist/components/Tabs/tabs-inline-action-geometry.js +43 -0
- package/dist/components/Tabs/tabs-inline-action-geometry.js.map +1 -0
- package/dist/components/Tabs/tabs.d.ts +14 -11
- package/dist/components/Tabs/tabs.d.ts.map +1 -1
- package/dist/components/Tabs/tabs.js +184 -69
- package/dist/components/Tabs/tabs.js.map +1 -1
- package/dist/components/Tag/index.d.ts +2 -0
- package/dist/components/Tag/index.d.ts.map +1 -0
- package/dist/components/Tag/index.js +7 -0
- package/dist/components/Tag/index.js.map +1 -0
- package/dist/components/Tag/tag.d.ts +39 -23
- package/dist/components/Tag/tag.d.ts.map +1 -1
- package/dist/components/Tag/tag.js +71 -63
- package/dist/components/Tag/tag.js.map +1 -1
- package/dist/components/Textarea/index.d.ts +2 -0
- package/dist/components/Textarea/index.d.ts.map +1 -0
- package/dist/components/Textarea/index.js +7 -0
- package/dist/components/Textarea/index.js.map +1 -0
- package/dist/components/Textarea/textarea.d.ts +24 -17
- package/dist/components/Textarea/textarea.d.ts.map +1 -1
- package/dist/components/Textarea/textarea.js +110 -72
- package/dist/components/Textarea/textarea.js.map +1 -1
- package/dist/components/TimePicker/index.d.ts +2 -0
- package/dist/components/TimePicker/index.d.ts.map +1 -0
- package/dist/components/TimePicker/index.js +6 -0
- package/dist/components/TimePicker/index.js.map +1 -0
- package/dist/components/TimePicker/time-columns.d.ts.map +1 -1
- package/dist/components/TimePicker/time-columns.js +44 -36
- package/dist/components/TimePicker/time-columns.js.map +1 -1
- package/dist/components/TimePicker/time-picker.d.ts +8 -7
- package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
- package/dist/components/TimePicker/time-picker.js +71 -53
- package/dist/components/TimePicker/time-picker.js.map +1 -1
- package/dist/components/Toast/index.d.ts +2 -0
- package/dist/components/Toast/index.d.ts.map +1 -0
- package/dist/components/Toast/index.js +7 -0
- package/dist/components/Toast/index.js.map +1 -0
- package/dist/components/Toast/toast.d.ts +2 -2
- package/dist/components/Toast/toast.d.ts.map +1 -1
- package/dist/components/Toast/toast.js +82 -20
- package/dist/components/Toast/toast.js.map +1 -1
- package/dist/components/Tooltip/index.d.ts +2 -0
- package/dist/components/Tooltip/index.d.ts.map +1 -0
- package/dist/components/Tooltip/index.js +9 -0
- package/dist/components/Tooltip/index.js.map +1 -0
- package/dist/components/Tooltip/tooltip.d.ts +2 -2
- package/dist/components/Tooltip/tooltip.d.ts.map +1 -1
- package/dist/components/Tooltip/tooltip.js +57 -25
- package/dist/components/Tooltip/tooltip.js.map +1 -1
- package/dist/components/TreeView/index.d.ts +2 -0
- package/dist/components/TreeView/index.d.ts.map +1 -0
- package/dist/components/TreeView/index.js +8 -0
- package/dist/components/TreeView/index.js.map +1 -0
- package/dist/components/TreeView/tree-view.d.ts +71 -14
- package/dist/components/TreeView/tree-view.d.ts.map +1 -1
- package/dist/components/TreeView/tree-view.js +313 -34
- package/dist/components/TreeView/tree-view.js.map +1 -1
- package/dist/hooks/use-controllable.d.ts.map +1 -1
- package/dist/hooks/use-controllable.js +4 -2
- package/dist/hooks/use-controllable.js.map +1 -1
- package/dist/hooks/use-is-touch-device.d.ts.map +1 -1
- package/dist/hooks/use-is-touch-device.js +3 -1
- package/dist/hooks/use-is-touch-device.js.map +1 -1
- package/dist/hooks/use-overflow-items.d.ts +10 -6
- package/dist/hooks/use-overflow-items.d.ts.map +1 -1
- package/dist/hooks/use-overflow-items.js.map +1 -1
- package/dist/hooks/use-truncated.d.ts +27 -0
- package/dist/hooks/use-truncated.d.ts.map +1 -0
- package/dist/hooks/use-truncated.js +57 -0
- package/dist/hooks/use-truncated.js.map +1 -0
- package/dist/{react-day-picker.css → index.css} +43 -0
- package/dist/index.d.ts +121 -67
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +140 -160
- package/dist/index.js.map +1 -1
- package/dist/lib/drag-visual.d.ts +2 -2
- package/dist/lib/drag-visual.js.map +1 -1
- package/dist/lib/utils.d.ts +1 -0
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +24 -2
- package/dist/lib/utils.js.map +1 -1
- package/dist/patterns/element-anatomy/index.d.ts +3 -0
- package/dist/patterns/element-anatomy/index.d.ts.map +1 -0
- package/dist/patterns/element-anatomy/index.js +22 -0
- package/dist/patterns/element-anatomy/index.js.map +1 -0
- package/dist/patterns/element-anatomy/item-anatomy.d.ts +32 -16
- package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.js +83 -35
- package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
- package/dist/patterns/element-anatomy/truncated-text.d.ts +27 -0
- package/dist/patterns/element-anatomy/truncated-text.d.ts.map +1 -0
- package/dist/patterns/element-anatomy/truncated-text.js +16 -0
- package/dist/patterns/element-anatomy/truncated-text.js.map +1 -0
- package/dist/patterns/header-canonical/chrome-header.d.ts +20 -4
- package/dist/patterns/header-canonical/chrome-header.d.ts.map +1 -1
- package/dist/patterns/header-canonical/chrome-header.js +4 -2
- package/dist/patterns/header-canonical/chrome-header.js.map +1 -1
- package/dist/patterns/header-canonical/index.d.ts +2 -0
- package/dist/patterns/header-canonical/index.d.ts.map +1 -0
- package/dist/patterns/header-canonical/index.js +6 -0
- package/dist/patterns/header-canonical/index.js.map +1 -0
- package/dist/patterns/horizontal-overflow/horizontal-overflow.d.ts +1 -1
- package/dist/patterns/horizontal-overflow/horizontal-overflow.js.map +1 -1
- package/dist/patterns/horizontal-overflow/index.d.ts +2 -0
- package/dist/patterns/horizontal-overflow/index.d.ts.map +1 -0
- package/dist/patterns/horizontal-overflow/index.js +14 -0
- package/dist/patterns/horizontal-overflow/index.js.map +1 -0
- package/dist/patterns/overlay-surface/index.d.ts +2 -0
- package/dist/patterns/overlay-surface/index.d.ts.map +1 -0
- package/dist/patterns/overlay-surface/index.js +8 -0
- package/dist/patterns/overlay-surface/index.js.map +1 -0
- package/dist/patterns/overlay-surface/overlay-surface.d.ts +12 -2
- package/dist/patterns/overlay-surface/overlay-surface.d.ts.map +1 -1
- package/dist/patterns/overlay-surface/overlay-surface.js +6 -2
- package/dist/patterns/overlay-surface/overlay-surface.js.map +1 -1
- package/dist/patterns/resize-handle/index.d.ts +2 -0
- package/dist/patterns/resize-handle/index.d.ts.map +1 -0
- package/dist/patterns/resize-handle/index.js +5 -0
- package/dist/patterns/resize-handle/index.js.map +1 -0
- package/dist/patterns/resize-handle/resize-handle.d.ts +9 -11
- package/dist/patterns/resize-handle/resize-handle.d.ts.map +1 -1
- package/dist/patterns/resize-handle/resize-handle.js +3 -6
- package/dist/patterns/resize-handle/resize-handle.js.map +1 -1
- package/dist/story-governance/category-matrix.d.ts +35 -0
- package/dist/story-governance/category-matrix.d.ts.map +1 -0
- package/dist/tokens/categorical-color.d.ts +60 -0
- package/dist/tokens/categorical-color.d.ts.map +1 -0
- package/dist/tokens/categorical-color.js +125 -0
- package/dist/tokens/categorical-color.js.map +1 -0
- package/dist/tokens/motion/motion.d.ts +8 -7
- package/dist/tokens/motion/motion.d.ts.map +1 -1
- package/dist/tokens/motion/motion.js +6 -6
- package/dist/tokens/motion/motion.js.map +1 -1
- package/dist/tokens/motion/overlay-motion.d.ts +19 -0
- package/dist/tokens/motion/overlay-motion.d.ts.map +1 -0
- package/dist/tokens/motion/overlay-motion.js +9 -0
- package/dist/tokens/motion/overlay-motion.js.map +1 -0
- package/dist/tokens/uiSize/icon-size.d.ts +3 -4
- package/dist/tokens/uiSize/icon-size.d.ts.map +1 -1
- package/dist/tokens/uiSize/icon-size.js +5 -0
- package/dist/tokens/uiSize/icon-size.js.map +1 -0
- package/ds-canonical/adapters/claude/agents/README.md +58 -0
- package/ds-canonical/adapters/claude/agents/canonical-reviewer.md +14 -0
- package/ds-canonical/adapters/claude-root-instructions.md +24 -0
- package/ds-canonical/adapters/claude-settings-base.json +169 -0
- package/ds-canonical/commands/README.md +9 -14
- package/ds-canonical/fork/AGENTS.md +110 -0
- package/ds-canonical/fork/CLAUDE.md +54 -0
- package/ds-canonical/fork/codex/agents/skills/bug-fix-rhythm/SKILL.md +207 -0
- package/ds-canonical/fork/codex/agents/skills/code-quality-audit/SKILL.md +71 -0
- package/ds-canonical/fork/codex/agents/skills/delivery-handoff/SKILL.md +244 -0
- package/ds-canonical/fork/codex/agents/skills/delivery-handoff/references/flow-diagram.md +180 -0
- package/ds-canonical/fork/codex/agents/skills/delivery-handoff/references/handoff-template.md +177 -0
- package/ds-canonical/fork/codex/agents/skills/delivery-handoff/references/inventory-checklist.md +196 -0
- package/ds-canonical/fork/codex/agents/skills/independent-review/SKILL.md +43 -0
- package/ds-canonical/fork/codex/agents/skills/independent-review/agents/openai.yaml +4 -0
- package/ds-canonical/fork/codex/agents/skills/independent-review/references/evidence-contract.md +23 -0
- package/ds-canonical/fork/codex/agents/skills/performance-audit/SKILL.md +114 -0
- package/ds-canonical/fork/codex/agents/skills/product-ui-audit/SKILL.md +252 -0
- package/ds-canonical/fork/codex/agents/skills/product-ui-audit/references/audit-checks.md +250 -0
- package/ds-canonical/fork/codex/agents/skills/product-ui-audit/references/common-misuses.md +329 -0
- package/ds-canonical/fork/codex/agents/skills/product-ui-audit/references/report-template.md +163 -0
- package/ds-canonical/fork/codex/agents/skills/propose-options/SKILL.md +190 -0
- package/ds-canonical/fork/codex/agents/skills/prototype/SKILL.md +246 -0
- package/ds-canonical/fork/codex/agents/skills/prototype/references/audit-checks.md +39 -0
- package/ds-canonical/fork/codex/agents/skills/prototype/references/benchmark-sources.md +94 -0
- package/ds-canonical/fork/codex/agents/skills/prototype/references/checkpoints.md +200 -0
- package/ds-canonical/fork/codex/agents/skills/prototype/references/evaluation-matrix.md +142 -0
- package/ds-canonical/fork/codex/agents/skills/prototype/references/ooux-template.md +198 -0
- package/ds-canonical/fork/codex/agents/skills/prototype/references/proposal-template.md +230 -0
- package/ds-canonical/fork/codex/agents/skills/scan-similar-bugs/SKILL.md +203 -0
- package/ds-canonical/fork/codex/agents/skills/ux-audit/SKILL.md +135 -0
- package/ds-canonical/fork/codex/agents/skills/visual-audit/SKILL.md +258 -0
- package/ds-canonical/fork/codex/agents/skills/visual-audit/output/.gitkeep +0 -0
- package/ds-canonical/fork/codex/agents/skills/visual-audit/references/audit-architecture.md +101 -0
- package/ds-canonical/fork/codex/agents/skills/visual-audit/references/visual-checklist.md +297 -0
- package/ds-canonical/fork/codex/agents/skills/visual-audit/references/world-class-benchmarks.md +198 -0
- package/ds-canonical/fork/codex/hooks.json +57 -0
- package/ds-canonical/fork/common/closed-tool-execution.mjs +1005 -0
- package/ds-canonical/fork/common/instruction.md +110 -0
- package/ds-canonical/fork/consumer/governance-check.mjs +3163 -0
- package/ds-canonical/fork/consumer/lib/closed-tool-execution.mjs +1005 -0
- package/ds-canonical/fork/consumer/lock.json +189 -0
- package/ds-canonical/fork/consumer/lock.schema.json +338 -0
- package/ds-canonical/fork/consumer/managed-files/0235cf6b3fd402a3717881dc205b5bf77aed7632e7d7d6f1811a40e3033bd816.blob +145 -0
- package/ds-canonical/fork/consumer/managed-files/145aab4b4fc470fc9ba7679b4a1d87d4f6a6d604940bd40f8d675c8af696498f.blob +53 -0
- package/ds-canonical/fork/consumer/managed-files/189e551431181e4a88783c02130d2787b801ef731c86e35bcde89a7313aa020e.blob +149 -0
- package/ds-canonical/fork/consumer/managed-files/24ad71c8613ddcf6fd23818cb3bb477a1fb6d83af4550b0bad43099813088686.blob +28 -0
- package/ds-canonical/fork/consumer/managed-files/2d1ef5617a5ce3c39ad9aae1c42af7d199f9270c0a0ccb490b63d62dbc6913db.blob +1005 -0
- package/ds-canonical/fork/consumer/managed-files/3625e02235cf166a50649a4d704cfb68fead69d0e168c481df013ebae9f6be25.blob +1412 -0
- package/ds-canonical/fork/consumer/managed-files/3ddedc67b464bdfe3dde9803a2d917920c96d633678b802eee638a44cd47366a.blob +8 -0
- package/ds-canonical/fork/consumer/managed-files/423729840acdd43c812f17c245b871716c6b60f76e4d3e94864d0e34a815bf8d.blob +348 -0
- package/ds-canonical/fork/consumer/managed-files/46f10bc55cdfdf834d0749eebff42ea0bb8576f361b36d2b6ba1851354c50150.blob +549 -0
- package/ds-canonical/fork/consumer/managed-files/475fba657eaeb8abd88f60635f3eadab27a9d0d3114ba4fffc65f43a2a0fe6e2.blob +124 -0
- package/ds-canonical/fork/consumer/managed-files/5aeda1df235815f40dd755951f2b868d4e02035653d4536a8dde209cdc75b493.blob +268 -0
- package/ds-canonical/fork/consumer/managed-files/61c6aca6d7f04bedbf556eaa9d3043681fe48cbe0e93a8ccb1d69a7c5e23fdcf.blob +113 -0
- package/ds-canonical/fork/consumer/managed-files/63061e05969667005020166ea2ae7df3c96663f7a928e7dd3eebc1471022df56.blob +2454 -0
- package/ds-canonical/fork/consumer/managed-files/65410b66e8799a560e07b71779c186fed2a0ef50575ac44745df7a0938a9c2b4.blob +321 -0
- package/ds-canonical/fork/consumer/managed-files/6b746628ac11fcccbe277ef6fea94cc2a7b90ea2822e83536eae80d5ee4b3fa5.blob +167 -0
- package/ds-canonical/fork/consumer/managed-files/76d485470d30ff014afacf7ce643763de9f4f956d9fc8128dbc1814774cf2e63.blob +671 -0
- package/ds-canonical/fork/consumer/managed-files/7937321877e5c34c27dae36c0d507d21cd42fa3f46a8f11a284b8b4a52b6faa4.blob +84 -0
- package/ds-canonical/fork/consumer/managed-files/794bb911ba8ebef58ab0d44a55b9968a19d2c62bcfb310b1ee3a84166f087f21.blob +924 -0
- package/ds-canonical/fork/consumer/managed-files/7ab15a8aeca39485d7b9b57234bbc4a69e0b9e70b4a851dfe10f6447127cee78.blob +51 -0
- package/ds-canonical/fork/consumer/managed-files/7b17835d0ceb17cdcdcaf930540a3e5d0943a34a747ac43c12ca8cd485c11581.blob +44 -0
- package/ds-canonical/fork/consumer/managed-files/7eb6cc60abfdabaf6617c336058fad1454ccde08889295767bfd9b7fd153cc6e.blob +900 -0
- package/ds-canonical/fork/consumer/managed-files/81037efa5b3b9f4a875704005fb268acdc9233bb901f44902e4e37e1da92b8e3.blob +46 -0
- package/ds-canonical/fork/consumer/managed-files/884721550b56308812b20917adac7a12f36fdb4ecda6b02460a7f5a219c0c966.blob +82 -0
- package/ds-canonical/fork/consumer/managed-files/91f414b9462ddc1e4be1d4c6ca4ac7a7340a6eec9e98ee109cbc049d3b354b6a.blob +36 -0
- package/ds-canonical/fork/consumer/managed-files/92e4828cdcb96ff0d01daa16f6885f24045a8e72820665650f2a1890dc3a446c.blob +121 -0
- package/ds-canonical/fork/consumer/managed-files/93a89f16f6717d1e4e4e27ec20ef916e3355c7aa890ca74cd9b12db30d8a899f.blob +36 -0
- package/ds-canonical/fork/consumer/managed-files/974521d6b8fe796ea4c58b3c78b0c2b8a1be262b97100d77aaf4cc9c7724bcab.blob +45 -0
- package/ds-canonical/fork/consumer/managed-files/9791c85388d2e2e2bd564e2550fadc3351c4c960a47ce838098afcba7544eb90.blob +973 -0
- package/ds-canonical/fork/consumer/managed-files/a448d2222133335c30e6fda5116e26a40db3283a19d62ccc0663c30d272262a7.blob +174 -0
- package/ds-canonical/fork/consumer/managed-files/ab072e7f2eca51f0e18a5e18c4b333260ee5d219a12cc6df5bb2d683f0b2734d.blob +2639 -0
- package/ds-canonical/fork/consumer/managed-files/bc44cabad181f766611dab6a8f52d81100afadd50ac59799015e8ba7bf46d9eb.blob +620 -0
- package/ds-canonical/fork/consumer/managed-files/c4aa767f11714467de0038a2149cf78074baddc17fc6a9e074b6aed7544c6f3c.blob +80 -0
- package/ds-canonical/fork/consumer/managed-files/c893e9b734b162f4c631fef871128c16a9ab565f796a0e76d7f320f821f220f5.blob +41 -0
- package/ds-canonical/fork/consumer/managed-files/cd9c61f8ff24fb9bcc3663e346e2cfa419cfb84e02aae18d3dc66dd192681696.blob +9 -0
- package/ds-canonical/fork/consumer/managed-files/da2ad2737315d25a5300905ddefd62f1695056a8a1af155c5b34170ad9923bce.blob +38 -0
- package/ds-canonical/fork/consumer/managed-files/e26057408661597aac4a57cdf7e30e2f9d0afb1b9a2dbbb692c3805cd9028ca5.blob +250 -0
- package/ds-canonical/fork/consumer/managed-files/e68ae1915965d8e17d4b2441c00c2b97fe062ea308ae8535c9eebb2fc16fa245.blob +20 -0
- package/ds-canonical/fork/consumer/managed-files/e71c65f45c6af7ce12b6b3c0d8028f185da5b719e2050c9b2a6b243d41ae4878.blob +182 -0
- package/ds-canonical/fork/consumer/managed-files/e813096d69c49812e40e00be9ac8fa14d77f0ec1f97ab4c45cb096b3bedd35de.blob +3 -0
- package/ds-canonical/fork/consumer/managed-files/e930ebca6fd8eeb30802aac0930efae7bcc44cc0996f24de07c11ca6a081fedf.blob +277 -0
- package/ds-canonical/fork/consumer/managed-files/ea94e914d5c8acb84ad7660e5bda0f01b6ccc8cf96b0f6b1ce048f8938d0f5b5.blob +47 -0
- package/ds-canonical/fork/consumer/managed-files/f4db6e616d5229aa7e700c6a3ace022cb9d90baae74e38ca65fce617f18e0d9b.blob +110 -0
- package/ds-canonical/fork/consumer/managed-files/faa128c26102415dc5345e68698efbbd28a395c35ee278dceae044cf430692b2.blob +11 -0
- package/ds-canonical/fork/governance.lock +1696 -0
- package/ds-canonical/fork/hooks/_log-fire.sh +143 -0
- package/ds-canonical/fork/hooks/block_prototype_imports.py +241 -0
- package/ds-canonical/fork/hooks/check_chrome_header_avatar_canonical.sh +106 -0
- package/ds-canonical/fork/hooks/check_consumer_app_invariants.sh +731 -0
- package/ds-canonical/fork/hooks/check_consumer_code_quality.sh +173 -0
- package/ds-canonical/fork/hooks/check_ds_anchor_preflight.sh +219 -0
- package/ds-canonical/fork/hooks/check_dynamic_tailwind_class.sh +57 -0
- package/ds-canonical/fork/hooks/check_escape_marker_abuse.sh +213 -0
- package/ds-canonical/fork/hooks/check_field_family_invariants.sh +376 -0
- package/ds-canonical/fork/hooks/check_fork_product_quality.sh +38 -0
- package/ds-canonical/fork/hooks/check_item_list_gap.sh +56 -0
- package/ds-canonical/fork/hooks/check_layout_space_magic_numbers.sh +160 -0
- package/ds-canonical/fork/hooks/check_opacity_token_usage.sh +319 -0
- package/ds-canonical/fork/hooks/check_pattern_invariants.sh +339 -0
- package/ds-canonical/fork/hooks/check_sidebar_menu_button_implicit_wrap.sh +99 -0
- package/ds-canonical/fork/hooks/check_story_determinism.sh +39 -0
- package/ds-canonical/fork/hooks/check_story_invariants.sh +400 -0
- package/ds-canonical/fork/hooks/check_tabs_content_chrome_body_double_gap.sh +241 -0
- package/ds-canonical/fork/hooks/check_tailwind_wildcard_in_docs.sh +86 -0
- package/ds-canonical/fork/hooks/inject_deploy_url_after_push.sh +333 -0
- package/ds-canonical/fork/hooks/lib/_hook_integrity.sh +158 -0
- package/ds-canonical/fork/hooks/lib/_micro_geometry.sh +291 -0
- package/ds-canonical/fork/hooks/lib/_provider_paths.sh +162 -0
- package/ds-canonical/fork/launchers/approval-evidence.mjs +1286 -0
- package/ds-canonical/fork/launchers/authority-decision-evidence.mjs +413 -0
- package/ds-canonical/fork/launchers/canonical-order.mjs +8 -0
- package/ds-canonical/fork/launchers/fork-governance-dispatcher.sh +154 -0
- package/ds-canonical/fork/launchers/format-provider-hook-output.mjs +25 -0
- package/ds-canonical/fork/launchers/independent-review.mjs +490 -0
- package/ds-canonical/fork/launchers/inject_fork_governance_preamble.sh +99 -0
- package/ds-canonical/fork/launchers/normalize-provider-event.mjs +355 -0
- package/ds-canonical/fork/launchers/product-hook-dispatch-runtime.mjs +1067 -0
- package/ds-canonical/fork/launchers/provider-hook-normalization.mjs +1640 -0
- package/ds-canonical/fork/launchers/provider-hook-output-transport.mjs +540 -0
- package/ds-canonical/fork/launchers/settings-hooks.json +35 -0
- package/ds-canonical/fork/manifest.json +1984 -0
- package/ds-canonical/fork/providers/claude/hook-config.json +327 -0
- package/ds-canonical/fork/providers/claude/instruction.md +54 -0
- package/ds-canonical/fork/providers/claude/skills/bug-fix-rhythm/SKILL.md +207 -0
- package/ds-canonical/fork/providers/claude/skills/code-quality-audit/SKILL.md +71 -0
- package/ds-canonical/fork/providers/claude/skills/delivery-handoff/SKILL.md +244 -0
- package/ds-canonical/fork/providers/claude/skills/delivery-handoff/references/flow-diagram.md +180 -0
- package/ds-canonical/fork/providers/claude/skills/delivery-handoff/references/handoff-template.md +177 -0
- package/ds-canonical/fork/providers/claude/skills/delivery-handoff/references/inventory-checklist.md +196 -0
- package/ds-canonical/fork/providers/claude/skills/independent-review/SKILL.md +43 -0
- package/ds-canonical/fork/providers/claude/skills/independent-review/agents/openai.yaml +4 -0
- package/ds-canonical/fork/providers/claude/skills/independent-review/references/evidence-contract.md +23 -0
- package/ds-canonical/fork/providers/claude/skills/performance-audit/SKILL.md +114 -0
- package/ds-canonical/fork/providers/claude/skills/product-ui-audit/SKILL.md +252 -0
- package/ds-canonical/fork/providers/claude/skills/product-ui-audit/references/audit-checks.md +250 -0
- package/ds-canonical/fork/providers/claude/skills/product-ui-audit/references/common-misuses.md +329 -0
- package/ds-canonical/fork/providers/claude/skills/product-ui-audit/references/report-template.md +163 -0
- package/ds-canonical/fork/providers/claude/skills/propose-options/SKILL.md +190 -0
- package/ds-canonical/fork/providers/claude/skills/prototype/SKILL.md +246 -0
- package/ds-canonical/fork/providers/claude/skills/prototype/references/audit-checks.md +39 -0
- package/ds-canonical/fork/providers/claude/skills/prototype/references/benchmark-sources.md +94 -0
- package/ds-canonical/fork/providers/claude/skills/prototype/references/checkpoints.md +200 -0
- package/ds-canonical/fork/providers/claude/skills/prototype/references/evaluation-matrix.md +142 -0
- package/ds-canonical/fork/providers/claude/skills/prototype/references/ooux-template.md +198 -0
- package/ds-canonical/fork/providers/claude/skills/prototype/references/proposal-template.md +230 -0
- package/ds-canonical/fork/providers/claude/skills/scan-similar-bugs/SKILL.md +203 -0
- package/ds-canonical/fork/providers/claude/skills/ux-audit/SKILL.md +135 -0
- package/ds-canonical/fork/providers/claude/skills/visual-audit/SKILL.md +258 -0
- package/ds-canonical/fork/providers/claude/skills/visual-audit/output/.gitkeep +0 -0
- package/ds-canonical/fork/providers/claude/skills/visual-audit/references/audit-architecture.md +101 -0
- package/ds-canonical/fork/providers/claude/skills/visual-audit/references/visual-checklist.md +297 -0
- package/ds-canonical/fork/providers/claude/skills/visual-audit/references/world-class-benchmarks.md +198 -0
- package/ds-canonical/fork/providers/codex/hook-config.json +57 -0
- package/ds-canonical/fork/providers/codex/skills/bug-fix-rhythm/SKILL.md +207 -0
- package/ds-canonical/fork/providers/codex/skills/code-quality-audit/SKILL.md +71 -0
- package/ds-canonical/fork/providers/codex/skills/delivery-handoff/SKILL.md +244 -0
- package/ds-canonical/fork/providers/codex/skills/delivery-handoff/references/flow-diagram.md +180 -0
- package/ds-canonical/fork/providers/codex/skills/delivery-handoff/references/handoff-template.md +177 -0
- package/ds-canonical/fork/providers/codex/skills/delivery-handoff/references/inventory-checklist.md +196 -0
- package/ds-canonical/fork/providers/codex/skills/independent-review/SKILL.md +43 -0
- package/ds-canonical/fork/providers/codex/skills/independent-review/agents/openai.yaml +4 -0
- package/ds-canonical/fork/providers/codex/skills/independent-review/references/evidence-contract.md +23 -0
- package/ds-canonical/fork/providers/codex/skills/performance-audit/SKILL.md +114 -0
- package/ds-canonical/fork/providers/codex/skills/product-ui-audit/SKILL.md +252 -0
- package/ds-canonical/fork/providers/codex/skills/product-ui-audit/references/audit-checks.md +250 -0
- package/ds-canonical/fork/providers/codex/skills/product-ui-audit/references/common-misuses.md +329 -0
- package/ds-canonical/fork/providers/codex/skills/product-ui-audit/references/report-template.md +163 -0
- package/ds-canonical/fork/providers/codex/skills/propose-options/SKILL.md +190 -0
- package/ds-canonical/fork/providers/codex/skills/prototype/SKILL.md +246 -0
- package/ds-canonical/fork/providers/codex/skills/prototype/references/audit-checks.md +39 -0
- package/ds-canonical/fork/providers/codex/skills/prototype/references/benchmark-sources.md +94 -0
- package/ds-canonical/fork/providers/codex/skills/prototype/references/checkpoints.md +200 -0
- package/ds-canonical/fork/providers/codex/skills/prototype/references/evaluation-matrix.md +142 -0
- package/ds-canonical/fork/providers/codex/skills/prototype/references/ooux-template.md +198 -0
- package/ds-canonical/fork/providers/codex/skills/prototype/references/proposal-template.md +230 -0
- package/ds-canonical/fork/providers/codex/skills/scan-similar-bugs/SKILL.md +203 -0
- package/ds-canonical/fork/providers/codex/skills/ux-audit/SKILL.md +135 -0
- package/ds-canonical/fork/providers/codex/skills/visual-audit/SKILL.md +258 -0
- package/ds-canonical/fork/providers/codex/skills/visual-audit/output/.gitkeep +0 -0
- package/ds-canonical/fork/providers/codex/skills/visual-audit/references/audit-architecture.md +101 -0
- package/ds-canonical/fork/providers/codex/skills/visual-audit/references/visual-checklist.md +297 -0
- package/ds-canonical/fork/providers/codex/skills/visual-audit/references/world-class-benchmarks.md +198 -0
- package/ds-canonical/fork/references/story-baseline-registry.json +123 -0
- package/ds-canonical/fork/references/story-baseline-registry.schema.json +145 -0
- package/ds-canonical/fork/references/utility-registry.json +426 -0
- package/ds-canonical/fork/references/utility-registry.schema.json +409 -0
- package/ds-canonical/fork/review/canonical-order.mjs +8 -0
- package/ds-canonical/fork/review/provider-review-binding.mjs +2377 -0
- package/ds-canonical/fork/skills/bug-fix-rhythm/SKILL.md +196 -0
- package/ds-canonical/fork/skills/code-quality-audit/SKILL.md +69 -0
- package/ds-canonical/fork/skills/delivery-handoff/SKILL.md +242 -0
- package/ds-canonical/fork/skills/delivery-handoff/references/flow-diagram.md +180 -0
- package/ds-canonical/fork/skills/delivery-handoff/references/handoff-template.md +177 -0
- package/ds-canonical/fork/skills/delivery-handoff/references/inventory-checklist.md +196 -0
- package/ds-canonical/fork/skills/independent-review/SKILL.md +37 -0
- package/ds-canonical/fork/skills/independent-review/agents/openai.yaml +4 -0
- package/ds-canonical/fork/skills/independent-review/references/evidence-contract.md +23 -0
- package/ds-canonical/fork/skills/performance-audit/SKILL.md +112 -0
- package/ds-canonical/fork/skills/product-ui-audit/SKILL.md +250 -0
- package/ds-canonical/fork/skills/product-ui-audit/references/audit-checks.md +250 -0
- package/ds-canonical/fork/skills/product-ui-audit/references/common-misuses.md +329 -0
- package/ds-canonical/fork/skills/product-ui-audit/references/report-template.md +163 -0
- package/ds-canonical/fork/skills/propose-options/SKILL.md +179 -0
- package/ds-canonical/fork/skills/prototype/SKILL.md +244 -0
- package/ds-canonical/fork/skills/prototype/references/audit-checks.md +39 -0
- package/ds-canonical/fork/skills/prototype/references/benchmark-sources.md +94 -0
- package/ds-canonical/fork/skills/prototype/references/checkpoints.md +200 -0
- package/ds-canonical/fork/skills/prototype/references/evaluation-matrix.md +142 -0
- package/ds-canonical/fork/skills/prototype/references/ooux-template.md +198 -0
- package/ds-canonical/fork/skills/prototype/references/proposal-template.md +230 -0
- package/ds-canonical/fork/skills/scan-similar-bugs/SKILL.md +201 -0
- package/ds-canonical/fork/skills/ux-audit/SKILL.md +133 -0
- package/ds-canonical/fork/skills/visual-audit/SKILL.md +248 -0
- package/ds-canonical/fork/skills/visual-audit/output/.gitkeep +0 -0
- package/ds-canonical/fork/skills/visual-audit/references/audit-architecture.md +101 -0
- package/ds-canonical/fork/skills/visual-audit/references/visual-checklist.md +297 -0
- package/ds-canonical/fork/skills/visual-audit/references/world-class-benchmarks.md +198 -0
- package/ds-canonical/hooks/README.md +39 -25
- package/ds-canonical/hooks/_log-fire.sh +111 -12
- package/ds-canonical/hooks/auto_regen_ds_barrel.sh +90 -0
- package/ds-canonical/hooks/block_prototype_imports.py +156 -26
- package/ds-canonical/hooks/check_audit_post_report_validator.sh +328 -32
- package/ds-canonical/hooks/check_audit_sample_escape.sh +10 -7
- package/ds-canonical/hooks/check_benchmark_citation.sh +41 -8
- package/ds-canonical/hooks/check_canonical_propagation.sh +85 -24
- package/ds-canonical/hooks/check_chrome_header_avatar_canonical.sh +106 -0
- package/ds-canonical/hooks/check_codex_brief_invariants.sh +138 -29
- package/ds-canonical/hooks/check_codex_collab_5step.sh +31 -25
- package/ds-canonical/hooks/check_consumer_app_invariants.sh +731 -0
- package/ds-canonical/hooks/check_consumer_code_quality.sh +173 -0
- package/ds-canonical/hooks/check_datatable_invariants.sh +173 -29
- package/ds-canonical/hooks/check_dim_count_drift.sh +8 -7
- package/ds-canonical/hooks/check_ds_anchor_preflight.sh +219 -0
- package/ds-canonical/hooks/check_dynamic_tailwind_class.sh +57 -0
- package/ds-canonical/hooks/check_escape_marker_abuse.sh +213 -0
- package/ds-canonical/hooks/check_field_controls_contracts.sh +37 -8
- package/ds-canonical/hooks/check_field_family_invariants.sh +195 -24
- package/ds-canonical/hooks/check_file_size_budget.sh +32 -14
- package/ds-canonical/hooks/check_full_story_visual_interaction_sweep.sh +111 -0
- package/ds-canonical/hooks/check_item_list_gap.sh +56 -0
- package/ds-canonical/hooks/check_layout_space_magic_numbers.sh +160 -0
- package/ds-canonical/hooks/check_main_branch_workbench.sh +27 -68
- package/ds-canonical/hooks/check_naming_and_abstraction.sh +21 -10
- package/ds-canonical/hooks/check_opacity_token_usage.sh +238 -68
- package/ds-canonical/hooks/check_orphan_ds_css.sh +96 -0
- package/ds-canonical/hooks/check_overlay_open_focus_escape_probe.sh +110 -0
- package/ds-canonical/hooks/check_pattern_invariants.sh +160 -15
- package/ds-canonical/hooks/check_peoplepicker_ssot_drift.sh +4 -4
- package/ds-canonical/hooks/check_pixel_quantified_audit.sh +6 -4
- package/ds-canonical/hooks/check_plugin_fork_health.sh +282 -0
- package/ds-canonical/hooks/check_propose_discipline.sh +255 -0
- package/ds-canonical/hooks/check_propose_pre_grep_verify.sh +6 -4
- package/ds-canonical/hooks/check_propose_without_benchmark.sh +71 -0
- package/ds-canonical/hooks/check_select_all_canonical.sh +1 -1
- package/ds-canonical/hooks/check_sidebar_menu_button_implicit_wrap.sh +99 -0
- package/ds-canonical/hooks/check_solo_workflow.sh +62 -168
- package/ds-canonical/hooks/check_spec_class_drift.sh +33 -19
- package/ds-canonical/hooks/check_ssot_header_declaration.sh +56 -0
- package/ds-canonical/hooks/check_story_determinism.sh +39 -0
- package/ds-canonical/hooks/check_story_invariants.sh +722 -222
- package/ds-canonical/hooks/check_storybook_addon_packaging.sh +201 -0
- package/ds-canonical/hooks/check_substantive_edit_approval_preflight.sh +143 -64
- package/ds-canonical/hooks/check_tabs_content_chrome_body_double_gap.sh +241 -0
- package/ds-canonical/hooks/check_tailwind_wildcard_in_docs.sh +86 -0
- package/ds-canonical/hooks/check_wrapper_primitive_schema_drift.sh +15 -12
- package/ds-canonical/hooks/chrome_header_dispatcher.sh +105 -0
- package/ds-canonical/hooks/enforce_home_charter.sh +31 -10
- package/ds-canonical/hooks/governance_control_plane.sh +15 -0
- package/ds-canonical/hooks/inject_deploy_url_after_push.sh +333 -0
- package/ds-canonical/hooks/inject_pending_self_audit.sh +68 -21
- package/ds-canonical/hooks/lib/_app_shell_primary_header_consistency.sh +148 -0
- package/ds-canonical/hooks/lib/_chrome_header_handcraft.sh +121 -0
- package/ds-canonical/hooks/lib/_code_quality.sh +9 -9
- package/ds-canonical/hooks/lib/_cva_default_sync.sh +11 -13
- package/ds-canonical/hooks/lib/_governance_coverage_check.sh +48 -21
- package/ds-canonical/hooks/lib/_hardcoded_strings.sh +18 -10
- package/ds-canonical/hooks/lib/_header_with_tabs_border.sh +107 -0
- package/ds-canonical/hooks/lib/_hook_integrity.sh +158 -0
- package/ds-canonical/hooks/lib/_layout_space_canonical.sh +5 -5
- package/ds-canonical/hooks/lib/_micro_geometry.sh +291 -0
- package/ds-canonical/hooks/lib/_overlay_handcraft.sh +42 -23
- package/ds-canonical/hooks/lib/_person_data_richness.sh +5 -5
- package/ds-canonical/hooks/lib/_provider_paths.sh +162 -0
- package/ds-canonical/hooks/lib/_story_compile_drift.sh +5 -5
- package/ds-canonical/hooks/lib/_tab_lg_chrome_header_equal.sh +290 -0
- package/ds-canonical/hooks/lib/_token_hygiene.sh +55 -20
- package/ds-canonical/hooks/lib/approval-evidence.mjs +1286 -0
- package/ds-canonical/hooks/lib/audit-report-validator.mjs +122 -0
- package/ds-canonical/hooks/lib/provider-marker-regex.mjs +23 -0
- package/ds-canonical/hooks/lib/tsx-governance-analysis.mjs +410 -0
- package/ds-canonical/hooks/log_governance_fires.sh +45 -13
- package/ds-canonical/hooks/log_skill_invokes.sh +8 -7
- package/ds-canonical/hooks/post_edit_dispatcher.sh +106 -28
- package/ds-canonical/hooks/registrations.json +488 -0
- package/ds-canonical/hooks/registrations.schema.json +58 -0
- package/ds-canonical/hooks/retired/2026-06-11-prune-merge/check_addon_subdir_ship.sh +76 -0
- package/ds-canonical/hooks/retired/2026-06-11-prune-merge/check_consumer_app_story_title.sh +81 -0
- package/ds-canonical/hooks/retired/2026-06-11-prune-merge/check_consumer_ds_primitive_misuse.sh +120 -0
- package/ds-canonical/hooks/retired/2026-06-11-prune-merge/check_consumer_no_ds_catalog.sh +101 -0
- package/ds-canonical/hooks/retired/2026-06-11-prune-merge/check_consumer_story_baseline.sh +76 -0
- package/ds-canonical/hooks/retired/2026-06-11-prune-merge/check_data_table_size_num_to_meta_width.sh +68 -0
- package/ds-canonical/hooks/retired/2026-06-11-prune-merge/check_fork_user_plugin_install.sh +79 -0
- package/ds-canonical/hooks/retired/2026-06-11-prune-merge/check_plugin_freshness.sh +68 -0
- package/ds-canonical/hooks/retired/2026-06-11-prune-merge/check_propose_cite_required.sh +88 -0
- package/ds-canonical/hooks/{check_propose_plain_chinese.sh → retired/2026-06-11-prune-merge/check_propose_plain_chinese.sh} +2 -2
- package/ds-canonical/hooks/retired/2026-06-11-prune-merge/check_storybook_addon_preset_cjs.sh +87 -0
- package/ds-canonical/hooks/{lib/_approval_re.sh → retired/_approval_re.sh.retired-2026-08-18} +6 -7
- package/ds-canonical/hooks/retired/check_container_breathing.sh +1 -1
- package/ds-canonical/hooks/retired/check_governance_compliance.sh +1 -1
- package/ds-canonical/hooks/session_start_governance_check.sh +157 -69
- package/ds-canonical/hooks/stop_audit_content_quality.sh +20 -0
- package/ds-canonical/hooks/stop_extract_canonical_rules.sh +20 -0
- package/ds-canonical/hooks/stop_passive_logging.sh +133 -59
- package/ds-canonical/hooks/stop_self_audit.sh +472 -178
- package/ds-canonical/hooks/tests/fixtures/transcripts/claude-transcript-jsonl-v1.jsonl +3 -0
- package/ds-canonical/hooks/tests/fixtures/transcripts/claude-transcript-jsonl-v2.jsonl +3 -0
- package/ds-canonical/hooks/tests/registration-output-contracts.mjs +151 -0
- package/ds-canonical/hooks/tests/run-all.sh +35 -5
- package/ds-canonical/hooks/tests/test_auto_regen_ds_barrel.sh +224 -0
- package/ds-canonical/hooks/tests/test_block_prototype_imports.sh +181 -9
- package/ds-canonical/hooks/tests/test_check_addon_subdir_ship.sh +171 -0
- package/ds-canonical/hooks/tests/test_check_app_shell_primary_header_consistency.sh +275 -81
- package/ds-canonical/hooks/tests/test_check_audit_post_report_validator.sh +292 -14
- package/ds-canonical/hooks/tests/test_check_audit_sample_escape.sh +0 -0
- package/ds-canonical/hooks/tests/test_check_benchmark_citation.sh +41 -9
- package/ds-canonical/hooks/tests/test_check_canonical_propagation.sh +56 -5
- package/ds-canonical/hooks/tests/test_check_chrome_header_avatar_canonical.sh +198 -0
- package/ds-canonical/hooks/tests/test_check_chrome_header_handcraft.sh +197 -81
- package/ds-canonical/hooks/tests/test_check_codex_brief_invariants.sh +375 -0
- package/ds-canonical/hooks/tests/test_check_codex_collab_5step.sh +49 -5
- package/ds-canonical/hooks/tests/test_check_consumer_app_invariants.sh +140 -0
- package/ds-canonical/hooks/tests/test_check_consumer_app_story_title.sh +164 -0
- package/ds-canonical/hooks/tests/test_check_consumer_code_quality.sh +86 -0
- package/ds-canonical/hooks/tests/test_check_consumer_ds_primitive_misuse.sh +417 -0
- package/ds-canonical/hooks/tests/test_check_consumer_no_ds_catalog.sh +214 -0
- package/ds-canonical/hooks/tests/test_check_consumer_story_baseline.sh +209 -0
- package/ds-canonical/hooks/tests/test_check_data_table_size_num_to_meta_width.sh +71 -0
- package/ds-canonical/hooks/tests/test_check_datatable_invariants.sh +126 -12
- package/ds-canonical/hooks/tests/test_check_dim_count_drift.sh +13 -8
- package/ds-canonical/hooks/tests/test_check_ds_anchor_preflight.sh +150 -0
- package/ds-canonical/hooks/tests/test_check_dynamic_tailwind_class.sh +76 -0
- package/ds-canonical/hooks/tests/test_check_escape_marker_abuse.sh +278 -0
- package/ds-canonical/hooks/tests/test_check_field_family_invariants.sh +108 -18
- package/ds-canonical/hooks/tests/test_check_file_size_budget.sh +67 -5
- package/ds-canonical/hooks/tests/test_check_fork_user_plugin_install.sh +151 -0
- package/ds-canonical/hooks/tests/test_check_full_story_visual_interaction_sweep.sh +200 -0
- package/ds-canonical/hooks/tests/test_check_header_with_tabs_border.sh +178 -68
- package/ds-canonical/hooks/tests/test_check_item_list_gap.sh +44 -0
- package/ds-canonical/hooks/tests/test_check_layout_space_magic_numbers.sh +264 -0
- package/ds-canonical/hooks/tests/test_check_main_branch_workbench.sh +45 -110
- package/ds-canonical/hooks/tests/test_check_naming_and_abstraction.sh +19 -2
- package/ds-canonical/hooks/tests/test_check_opacity_token_usage.sh +151 -6
- package/ds-canonical/hooks/tests/test_check_orphan_ds_css.sh +228 -0
- package/ds-canonical/hooks/tests/test_check_overlay_open_focus_escape_probe.sh +191 -0
- package/ds-canonical/hooks/tests/test_check_pattern_invariants.sh +103 -7
- package/ds-canonical/hooks/tests/test_check_plugin_fork_health.sh +132 -0
- package/ds-canonical/hooks/tests/test_check_propose_cite_required.sh +181 -0
- package/ds-canonical/hooks/tests/test_check_propose_discipline.sh +69 -0
- package/ds-canonical/hooks/tests/test_check_propose_plain_chinese.sh +10 -7
- package/ds-canonical/hooks/tests/test_check_propose_pre_grep_verify.sh +5 -5
- package/ds-canonical/hooks/tests/test_check_propose_without_benchmark.sh +100 -0
- package/ds-canonical/hooks/tests/test_check_sidebar_menu_button_implicit_wrap.sh +224 -0
- package/ds-canonical/hooks/tests/test_check_solo_workflow.sh +86 -105
- package/ds-canonical/hooks/tests/test_check_spec_class_drift.sh +35 -7
- package/ds-canonical/hooks/tests/test_check_ssot_header_declaration.sh +72 -0
- package/ds-canonical/hooks/tests/{test_check_story_anatomy.sh.broken → test_check_story_anatomy.sh} +73 -4
- package/ds-canonical/hooks/tests/test_check_story_category.sh +411 -0
- package/ds-canonical/hooks/tests/test_check_story_determinism.sh +39 -0
- package/ds-canonical/hooks/tests/test_check_story_invariants.sh +734 -43
- package/ds-canonical/hooks/tests/{test_check_story_name_jargon.sh.broken → test_check_story_name_jargon.sh} +22 -1
- package/ds-canonical/hooks/tests/test_check_storybook_addon_packaging.sh +89 -0
- package/ds-canonical/hooks/tests/test_check_storybook_addon_preset_cjs.sh +200 -0
- package/ds-canonical/hooks/tests/test_check_substantive_edit_approval_preflight.sh +626 -28
- package/ds-canonical/hooks/tests/test_check_tab_lg_chrome_header_equal.sh +182 -97
- package/ds-canonical/hooks/tests/test_check_tabs_content_chrome_body_double_gap.sh +76 -0
- package/ds-canonical/hooks/tests/test_check_tailwind_wildcard_in_docs.sh +182 -0
- package/ds-canonical/hooks/tests/test_check_token_hygiene.sh +63 -15
- package/ds-canonical/hooks/tests/test_check_wrapper_primitive_schema_drift.sh +12 -6
- package/ds-canonical/hooks/tests/test_chrome_header_dispatcher.sh +125 -0
- package/ds-canonical/hooks/tests/test_enforce_home_charter.sh +58 -4
- package/ds-canonical/hooks/tests/test_governance_control_plane.sh +66 -0
- package/ds-canonical/hooks/tests/test_governance_coverage_check.sh +88 -0
- package/ds-canonical/hooks/tests/test_hook_integrity_classification.sh +217 -0
- package/ds-canonical/hooks/tests/test_inject_deploy_url_after_push.sh +276 -0
- package/ds-canonical/hooks/tests/test_inject_pending_self_audit.sh +116 -11
- package/ds-canonical/hooks/tests/test_log_governance_fires.sh +117 -1
- package/ds-canonical/hooks/tests/test_log_skill_invokes.sh +74 -5
- package/ds-canonical/hooks/tests/test_post_edit_dispatcher.sh +234 -77
- package/ds-canonical/hooks/tests/test_provider_peer_symmetry.sh +76 -0
- package/ds-canonical/hooks/tests/test_registration_output_contracts.sh +20 -0
- package/ds-canonical/hooks/tests/test_session_start_governance_check.sh +145 -22
- package/ds-canonical/hooks/tests/test_stop_audit_content_quality.sh +75 -0
- package/ds-canonical/hooks/tests/test_stop_capture_metrics.sh +101 -23
- package/ds-canonical/hooks/tests/test_stop_extract_canonical_rules.sh +75 -0
- package/ds-canonical/hooks/tests/{test_stop_governance_drift_check.sh.broken → test_stop_governance_drift_check.sh} +22 -12
- package/ds-canonical/hooks/tests/test_stop_passive_logging.sh +102 -1
- package/ds-canonical/hooks/tests/test_stop_repository_command_context.sh +38 -0
- package/ds-canonical/hooks/tests/test_stop_self_audit.sh +427 -13
- package/ds-canonical/references/README.md +19 -11
- package/ds-canonical/references/build-ui-canonicals.md +23 -6
- package/ds-canonical/references/certified-surfaces.md +46 -0
- package/ds-canonical/references/composition-fidelity.md +47 -0
- package/ds-canonical/references/cva-patterns.md +2 -2
- package/ds-canonical/references/drag-canonical.md +67 -311
- package/ds-canonical/references/failure-class-registry.json +160 -0
- package/ds-canonical/references/governance-audit-coverage.md +34 -0
- package/ds-canonical/references/item-anatomy-recipe.md +3 -3
- package/ds-canonical/references/naming-conventions.md +2 -1
- package/ds-canonical/references/principle-dim-map.json +136 -9
- package/ds-canonical/references/props-naming.md +18 -3
- package/ds-canonical/references/repository-hygiene-policy.json +143 -0
- package/ds-canonical/references/repository-hygiene.md +106 -0
- package/ds-canonical/references/runtime-evidence-retention.md +88 -0
- package/ds-canonical/references/scenario-definition.md +146 -0
- package/ds-canonical/references/spec-rules.md +1 -1
- package/ds-canonical/references/ssot-consultation.md +5 -5
- package/ds-canonical/references/ssot-index.md +11 -10
- package/ds-canonical/references/story-baseline-registry.json +55 -11
- package/ds-canonical/references/story-baseline-registry.schema.json +145 -0
- package/ds-canonical/references/structural-token-retention.md +7 -10
- package/ds-canonical/references/tailwind-gotchas.md +4 -4
- package/ds-canonical/references/ui-dev-rules.md +22 -1
- package/ds-canonical/rules/README.md +15 -10
- package/ds-canonical/rules/meta-patterns.md +30 -30
- package/ds-canonical/rules/self-verify.md +22 -12
- package/ds-canonical/rules/spec-rules.md +2 -2
- package/ds-canonical/rules/story-rules.md +36 -9
- package/ds-canonical/rules/ui-development.md +23 -15
- package/ds-canonical/skills/README.md +29 -17
- package/ds-canonical/skills/bug-fix-rhythm/SKILL.md +26 -13
- package/ds-canonical/skills/canonical-reviewer/SKILL.md +29 -0
- package/ds-canonical/skills/code-quality-audit/SKILL.md +15 -11
- package/ds-canonical/skills/codex-collab/SKILL.md +22 -235
- package/ds-canonical/skills/codex-collab/references/brief-template.md +37 -43
- package/ds-canonical/skills/codex-collab/references/transport.md +38 -57
- package/ds-canonical/skills/codify-corrections/SKILL.md +37 -36
- package/ds-canonical/skills/codify-principle/SKILL.md +35 -34
- package/ds-canonical/skills/component-quality-gate/SKILL.md +17 -17
- package/ds-canonical/skills/component-quality-gate/references/checklist.md +8 -8
- package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +47 -224
- package/ds-canonical/skills/deep-audit-cross-codex/references/phase-a-workflow.md +36 -102
- package/ds-canonical/skills/deep-audit-cross-codex/references/phase-b-codex-brief.md +33 -147
- package/ds-canonical/skills/deep-audit-cross-codex/references/skill-relationships-antipatterns-benchmarks.md +28 -0
- package/ds-canonical/skills/deep-audit-cross-codex/references/triage-rubric.md +15 -80
- package/ds-canonical/skills/deep-audit-cross-codex/references/upstream-directives-r-mapping.md +34 -0
- package/ds-canonical/skills/delivery-handoff/SKILL.md +19 -8
- package/ds-canonical/skills/design-system-audit/SKILL.md +130 -76
- package/ds-canonical/skills/design-system-audit/references/audit-prompts.md +207 -105
- package/ds-canonical/skills/design-system-audit/references/checkpoints.md +59 -50
- package/ds-canonical/skills/design-system-audit/references/historical-bugs.md +33 -9
- package/ds-canonical/skills/design-system-audit/references/principle-audit-protocol.md +51 -32
- package/ds-canonical/skills/design-system-audit/references/rule-placement.md +57 -53
- package/ds-canonical/skills/ensure-canonical/SKILL.md +37 -35
- package/ds-canonical/skills/governance-health/SKILL.md +31 -25
- package/ds-canonical/skills/governance-status/SKILL.md +30 -0
- package/ds-canonical/skills/independent-review/SKILL.md +37 -0
- package/ds-canonical/skills/independent-review/agents/openai.yaml +4 -0
- package/ds-canonical/skills/independent-review/references/evidence-contract.md +23 -0
- package/ds-canonical/skills/knowledge-prune/SKILL.md +57 -114
- package/ds-canonical/skills/knowledge-prune/references/phase-z-cross-repo-ssot-propagation.md +15 -0
- package/ds-canonical/skills/knowledge-prune/references/prune-dimensions-d5-d10.md +57 -0
- package/ds-canonical/skills/knowledge-prune/references/world-class-prune-alignment.md +7 -0
- package/ds-canonical/skills/new-component/SKILL.md +33 -25
- package/ds-canonical/skills/new-component/references/new-component-checklist.md +12 -11
- package/ds-canonical/skills/performance-audit/SKILL.md +15 -12
- package/ds-canonical/skills/product-ui-audit/SKILL.md +52 -34
- package/ds-canonical/skills/product-ui-audit/references/audit-checks.md +10 -7
- package/ds-canonical/skills/product-ui-audit/references/report-template.md +30 -26
- package/ds-canonical/skills/propose-options/SKILL.md +23 -23
- package/ds-canonical/skills/prototype/SKILL.md +19 -21
- package/ds-canonical/skills/prototype/references/audit-checks.md +6 -5
- package/ds-canonical/skills/prototype/references/checkpoints.md +26 -17
- package/ds-canonical/skills/prototype/references/evaluation-matrix.md +2 -2
- package/ds-canonical/skills/prototype/references/ooux-template.md +1 -1
- package/ds-canonical/skills/prototype/references/proposal-template.md +5 -5
- package/ds-canonical/skills/scan-similar-bugs/SKILL.md +29 -28
- package/ds-canonical/skills/story-auto-compile-migrate/SKILL.md +26 -25
- package/ds-canonical/skills/story-writing/SKILL.md +25 -14
- package/ds-canonical/skills/story-writing/references/anatomy-standard.md +2 -2
- package/ds-canonical/skills/story-writing/references/category-templates.md +15 -15
- package/ds-canonical/skills/ux-audit/SKILL.md +13 -10
- package/ds-canonical/skills/visual-audit/SKILL.md +47 -46
- package/ds-canonical/skills/visual-audit/references/audit-architecture.md +7 -7
- package/ds-canonical/skills/visual-audit/references/visual-checklist.md +14 -14
- package/ds-canonical/skills/visual-audit/references/world-class-benchmarks.md +3 -3
- package/ds-canonical/templates/_README.md +22 -0
- package/ds-canonical/templates/consumer-governance.md +82 -0
- package/ds-canonical/templates/dashboard-app.tsx +186 -0
- package/ds-canonical/templates/product-launchers/fork-governance-dispatcher.sh +154 -0
- package/ds-canonical/templates/product-launchers/format-provider-hook-output.mjs +25 -0
- package/ds-canonical/templates/product-launchers/independent-review.mjs +490 -0
- package/ds-canonical/templates/product-launchers/inject_fork_governance_preamble.sh +99 -0
- package/ds-canonical/templates/product-launchers/normalize-provider-event.mjs +355 -0
- package/ds-canonical/templates/product-launchers/product-hook-dispatch-runtime.mjs +1067 -0
- package/ds-canonical/templates/product-provider-wiring.json +82 -0
- package/ds-canonical/templates/product-provider-wiring.schema.json +153 -0
- package/ds-story-manifest.json +1786 -0
- package/llms-full.txt +450 -0
- package/llms.txt +74 -0
- package/package.json +43 -10
- package/src/README.md +1 -1
- package/src/components/Accordion/accordion.anatomy.stories.tsx +496 -0
- package/src/components/Accordion/accordion.principles.stories.tsx +365 -0
- package/src/components/Accordion/accordion.spec.md +31 -5
- package/src/components/Accordion/accordion.stories.tsx +192 -0
- package/src/components/Accordion/accordion.tsx +13 -5
- package/src/components/Accordion/index.ts +5 -0
- package/src/components/AccountMenu/account-menu.anatomy.stories.tsx +117 -0
- package/src/components/AccountMenu/account-menu.principles.stories.tsx +99 -0
- package/src/components/AccountMenu/account-menu.spec.md +127 -0
- package/src/components/AccountMenu/account-menu.stories.tsx +65 -0
- package/src/components/AccountMenu/account-menu.tsx +194 -0
- package/src/components/AccountMenu/index.ts +5 -0
- package/src/components/AgentPanel/agent-fab.tsx +111 -0
- package/src/components/AgentPanel/agent-logo.tsx +417 -0
- package/src/components/AgentPanel/agent-panel.anatomy.stories.tsx +124 -0
- package/src/components/AgentPanel/agent-panel.css +43 -0
- package/src/components/AgentPanel/agent-panel.principles.stories.tsx +40 -0
- package/src/components/AgentPanel/agent-panel.spec.md +226 -0
- package/src/components/AgentPanel/agent-panel.stories.tsx +215 -0
- package/src/components/AgentPanel/agent-panel.tsx +0 -0
- package/src/components/AgentPanel/index.ts +5 -0
- package/src/components/Alert/alert.anatomy.stories.tsx +241 -0
- package/src/components/Alert/alert.principles.stories.tsx +214 -0
- package/src/components/Alert/alert.spec.md +28 -26
- package/src/components/Alert/alert.stories.tsx +136 -0
- package/src/components/Alert/alert.tsx +19 -8
- package/src/components/Alert/index.ts +5 -0
- package/src/components/AppShell/_demo-helpers.tsx +107 -28
- package/src/components/AppShell/app-shell.anatomy.stories.tsx +185 -0
- package/src/components/AppShell/app-shell.principles.stories.tsx +140 -0
- package/src/components/AppShell/app-shell.spec.md +84 -48
- package/src/components/AppShell/app-shell.stories.tsx +445 -0
- package/src/components/AppShell/app-shell.tsx +77 -24
- package/src/components/AppShell/index.ts +5 -0
- package/src/components/AspectRatio/aspect-ratio.anatomy.stories.tsx +208 -0
- package/src/components/AspectRatio/aspect-ratio.principles.stories.tsx +303 -0
- package/src/components/AspectRatio/aspect-ratio.spec.md +24 -12
- package/src/components/AspectRatio/aspect-ratio.stories.tsx +91 -0
- package/src/components/AspectRatio/aspect-ratio.tsx +6 -3
- package/src/components/AspectRatio/index.ts +5 -0
- package/src/components/Avatar/avatar.anatomy.stories.tsx +705 -0
- package/src/components/Avatar/avatar.principles.stories.tsx +249 -0
- package/src/components/Avatar/avatar.spec.md +66 -103
- package/src/components/Avatar/avatar.stories.tsx +156 -0
- package/src/components/Avatar/avatar.tsx +85 -64
- package/src/components/Avatar/index.ts +5 -0
- package/src/components/Badge/badge.anatomy.stories.tsx +591 -0
- package/src/components/Badge/badge.principles.stories.tsx +391 -0
- package/src/components/Badge/badge.spec.md +43 -23
- package/src/components/Badge/badge.stories.tsx +82 -0
- package/src/components/Badge/badge.tsx +38 -14
- package/src/components/Badge/index.ts +5 -0
- package/src/components/Breadcrumb/breadcrumb.anatomy.stories.tsx +462 -0
- package/src/components/Breadcrumb/breadcrumb.principles.stories.tsx +353 -0
- package/src/components/Breadcrumb/breadcrumb.spec.md +55 -43
- package/src/components/Breadcrumb/breadcrumb.stories.tsx +381 -0
- package/src/components/Breadcrumb/breadcrumb.tsx +43 -86
- package/src/components/Breadcrumb/index.ts +5 -0
- package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +225 -0
- package/src/components/BulkActionBar/bulk-action-bar.principles.stories.tsx +255 -0
- package/src/components/BulkActionBar/bulk-action-bar.spec.md +64 -36
- package/src/components/BulkActionBar/bulk-action-bar.stories.tsx +124 -0
- package/src/components/BulkActionBar/bulk-action-bar.tsx +26 -18
- package/src/components/BulkActionBar/index.ts +5 -0
- package/src/components/Button/button-group.tsx +64 -21
- package/src/components/Button/button.anatomy.stories.tsx +729 -0
- package/src/components/Button/button.principles.stories.tsx +369 -0
- package/src/components/Button/button.spec.md +42 -39
- package/src/components/Button/button.stories.tsx +368 -0
- package/src/components/Button/button.tsx +109 -55
- package/src/components/Button/index.ts +6 -0
- package/src/components/Calendar/calendar.anatomy.stories.tsx +237 -0
- package/src/components/Calendar/calendar.principles.stories.tsx +152 -0
- package/src/components/Calendar/calendar.spec.md +80 -55
- package/src/components/Calendar/calendar.stories.tsx +108 -0
- package/src/components/Calendar/calendar.tsx +200 -134
- package/src/components/Calendar/index.ts +5 -0
- package/src/components/Carousel/carousel.anatomy.stories.tsx +517 -0
- package/src/components/Carousel/carousel.principles.stories.tsx +334 -0
- package/src/components/Carousel/carousel.spec.md +51 -15
- package/src/components/Carousel/carousel.stories.tsx +125 -0
- package/src/components/Carousel/carousel.tsx +139 -56
- package/src/components/Carousel/index.ts +5 -0
- package/src/components/Chart/chart.anatomy.stories.tsx +310 -0
- package/src/components/Chart/chart.principles.stories.tsx +437 -0
- package/src/components/Chart/chart.spec.md +24 -5
- package/src/components/Chart/chart.stories.tsx +263 -0
- package/src/components/Chart/chart.tsx +10 -3
- package/src/components/Chart/index.ts +5 -0
- package/src/components/Checkbox/checkbox-group.tsx +28 -16
- package/src/components/Checkbox/checkbox.anatomy.stories.tsx +614 -0
- package/src/components/Checkbox/checkbox.principles.stories.tsx +216 -0
- package/src/components/Checkbox/checkbox.spec.md +65 -42
- package/src/components/Checkbox/checkbox.stories.tsx +119 -0
- package/src/components/Checkbox/checkbox.tsx +189 -32
- package/src/components/Checkbox/index.ts +6 -0
- package/src/components/Chip/chip.anatomy.stories.tsx +442 -0
- package/src/components/Chip/chip.principles.stories.tsx +228 -0
- package/src/components/Chip/chip.spec.md +52 -40
- package/src/components/Chip/chip.stories.tsx +111 -0
- package/src/components/Chip/chip.tsx +130 -45
- package/src/components/Chip/index.ts +5 -0
- package/src/components/CircularProgress/circular-progress.anatomy.stories.tsx +177 -0
- package/src/components/CircularProgress/circular-progress.principles.stories.tsx +265 -0
- package/src/components/CircularProgress/circular-progress.spec.md +32 -32
- package/src/components/CircularProgress/circular-progress.stories.tsx +149 -0
- package/src/components/CircularProgress/circular-progress.tsx +5 -6
- package/src/components/CircularProgress/index.ts +5 -0
- package/src/components/Coachmark/coachmark-story-helpers.tsx +39 -0
- package/src/components/Coachmark/coachmark.anatomy.stories.tsx +356 -0
- package/src/components/Coachmark/coachmark.principles.stories.tsx +356 -0
- package/src/components/Coachmark/coachmark.spec.md +36 -29
- package/src/components/Coachmark/coachmark.stories.tsx +220 -0
- package/src/components/Coachmark/coachmark.tsx +36 -10
- package/src/components/Coachmark/index.ts +5 -0
- package/src/components/Combobox/combobox.anatomy.stories.tsx +926 -0
- package/src/components/Combobox/combobox.principles.stories.tsx +238 -0
- package/src/components/Combobox/combobox.spec.md +45 -16
- package/src/components/Combobox/combobox.stories.tsx +194 -0
- package/src/components/Combobox/combobox.tsx +278 -97
- package/src/components/Combobox/index.ts +5 -0
- package/src/components/Command/command.anatomy.stories.tsx +107 -0
- package/src/components/Command/command.principles.stories.tsx +86 -0
- package/src/components/Command/command.spec.md +33 -23
- package/src/components/Command/command.stories.tsx +312 -0
- package/src/components/Command/command.tsx +28 -12
- package/src/components/Command/index.ts +5 -0
- package/src/components/DataTable/active-editor-controller.ts +4 -3
- package/src/components/DataTable/cell-registry.tsx +161 -120
- package/src/components/DataTable/column-types.ts +14 -18
- package/src/components/DataTable/data-table-column-visibility-panel.tsx +28 -12
- package/src/components/DataTable/data-table-filter-group.tsx +248 -0
- package/src/components/DataTable/data-table-filter-labels.ts +100 -0
- package/src/components/DataTable/data-table-filter-panel.tsx +85 -448
- package/src/components/DataTable/data-table-filter-value-picker.tsx +254 -0
- package/src/components/DataTable/data-table-interaction-layer.tsx +26 -29
- package/src/components/DataTable/data-table-sort-manager.tsx +19 -11
- package/src/components/DataTable/data-table.anatomy.stories.tsx +522 -0
- package/src/components/DataTable/data-table.css +34 -8
- package/src/components/DataTable/data-table.principles.stories.tsx +270 -0
- package/src/components/DataTable/data-table.spec.md +186 -172
- package/src/components/DataTable/data-table.stories.tsx +2311 -0
- package/src/components/DataTable/data-table.tsx +678 -278
- package/src/components/DataTable/filter-operators.spec.md +269 -0
- package/src/components/DataTable/filter-operators.ts +1 -1
- package/src/components/DataTable/filter-tree.ts +194 -44
- package/src/components/DataTable/index.ts +8 -0
- package/src/components/DateGrid/date-grid.anatomy.stories.tsx +587 -0
- package/src/components/DateGrid/date-grid.principles.stories.tsx +344 -0
- package/src/components/DateGrid/date-grid.spec.md +46 -41
- package/src/components/DateGrid/date-grid.stories.tsx +204 -0
- package/src/components/DateGrid/date-grid.tsx +89 -28
- package/src/components/DateGrid/index.ts +5 -0
- package/src/components/DatePicker/date-picker.anatomy.stories.tsx +946 -0
- package/src/components/DatePicker/date-picker.principles.stories.tsx +205 -0
- package/src/components/DatePicker/date-picker.spec.md +61 -57
- package/src/components/DatePicker/date-picker.stories.tsx +364 -0
- package/src/components/DatePicker/date-picker.tsx +382 -198
- package/src/components/DatePicker/index.ts +5 -0
- package/src/components/DescriptionList/description-list.anatomy.stories.tsx +254 -0
- package/src/components/DescriptionList/description-list.principles.stories.tsx +309 -0
- package/src/components/DescriptionList/description-list.spec.md +35 -30
- package/src/components/DescriptionList/description-list.stories.tsx +113 -0
- package/src/components/DescriptionList/description-list.tsx +16 -14
- package/src/components/DescriptionList/index.ts +5 -0
- package/src/components/Dialog/dialog.anatomy.stories.tsx +485 -0
- package/src/components/Dialog/dialog.principles.stories.tsx +261 -0
- package/src/components/Dialog/dialog.spec.md +58 -32
- package/src/components/Dialog/dialog.stories.tsx +548 -0
- package/src/components/Dialog/dialog.tsx +81 -36
- package/src/components/Dialog/index.ts +5 -0
- package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +971 -0
- package/src/components/DropdownMenu/dropdown-menu.principles.stories.tsx +341 -0
- package/src/components/DropdownMenu/dropdown-menu.spec.md +40 -22
- package/src/components/DropdownMenu/dropdown-menu.stories.tsx +276 -0
- package/src/components/DropdownMenu/dropdown-menu.tsx +153 -40
- package/src/components/DropdownMenu/index.ts +5 -0
- package/src/components/Empty/empty.anatomy.stories.tsx +275 -0
- package/src/components/Empty/empty.principles.stories.tsx +314 -0
- package/src/components/Empty/empty.spec.md +42 -42
- package/src/components/Empty/empty.stories.tsx +69 -0
- package/src/components/Empty/empty.tsx +23 -11
- package/src/components/Empty/index.ts +5 -0
- package/src/components/Field/field-context.ts +196 -4
- package/src/components/Field/field-controls.spec.md +161 -54
- package/src/components/Field/field-edit-keys.ts +66 -0
- package/src/components/Field/field-types.ts +71 -20
- package/src/components/Field/field-wrapper.tsx +139 -89
- package/src/components/Field/field.anatomy.stories.tsx +547 -0
- package/src/components/Field/field.principles.stories.tsx +255 -0
- package/src/components/Field/field.spec.md +76 -34
- package/src/components/Field/field.stories.tsx +751 -0
- package/src/components/Field/field.tsx +65 -12
- package/src/components/Field/form-validation.spec.md +60 -34
- package/src/components/Field/index.ts +6 -0
- package/src/components/Field/use-form-validation.ts +270 -0
- package/src/components/FieldControlGroup/field-control-group.anatomy.stories.tsx +119 -0
- package/src/components/FieldControlGroup/field-control-group.principles.stories.tsx +121 -0
- package/src/components/FieldControlGroup/field-control-group.spec.md +41 -19
- package/src/components/FieldControlGroup/field-control-group.stories.tsx +162 -0
- package/src/components/FieldControlGroup/field-control-group.tsx +13 -15
- package/src/components/FieldControlGroup/index.ts +5 -0
- package/src/components/FileItem/file-item.anatomy.stories.tsx +346 -0
- package/src/components/FileItem/file-item.principles.stories.tsx +245 -0
- package/src/components/FileItem/file-item.spec.md +133 -104
- package/src/components/FileItem/file-item.stories.tsx +289 -0
- package/src/components/FileItem/file-item.tsx +85 -39
- package/src/components/FileItem/index.ts +5 -0
- package/src/components/FileUpload/file-upload.anatomy.stories.tsx +446 -0
- package/src/components/FileUpload/file-upload.principles.stories.tsx +312 -0
- package/src/components/FileUpload/file-upload.spec.md +66 -24
- package/src/components/FileUpload/file-upload.stories.tsx +219 -0
- package/src/components/FileUpload/file-upload.tsx +138 -43
- package/src/components/FileUpload/index.ts +5 -0
- package/src/components/FileViewer/can-render-image.ts +15 -0
- package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +1130 -0
- package/src/components/FileViewer/file-viewer.principles.stories.tsx +636 -0
- package/src/components/FileViewer/file-viewer.spec.md +70 -58
- package/src/components/FileViewer/file-viewer.stories.tsx +417 -0
- package/src/components/FileViewer/file-viewer.tsx +157 -72
- package/src/components/FileViewer/image-renderer.tsx +38 -15
- package/src/components/FileViewer/index.ts +5 -0
- package/src/components/HoverCard/hover-card.anatomy.stories.tsx +151 -0
- package/src/components/HoverCard/hover-card.principles.stories.tsx +211 -0
- package/src/components/HoverCard/hover-card.spec.md +38 -28
- package/src/components/HoverCard/hover-card.stories.tsx +304 -0
- package/src/components/HoverCard/hover-card.tsx +23 -6
- package/src/components/HoverCard/index.ts +5 -0
- package/src/components/InlineEdit/index.ts +5 -0
- package/src/components/InlineEdit/inline-edit.anatomy.stories.tsx +250 -0
- package/src/components/InlineEdit/inline-edit.principles.stories.tsx +142 -0
- package/src/components/InlineEdit/inline-edit.spec.md +186 -0
- package/src/components/InlineEdit/inline-edit.stories.tsx +261 -0
- package/src/components/InlineEdit/inline-edit.tsx +455 -0
- package/src/components/Input/index.ts +5 -0
- package/src/components/Input/input.anatomy.stories.tsx +780 -0
- package/src/components/Input/input.principles.stories.tsx +260 -0
- package/src/components/Input/input.spec.md +34 -26
- package/src/components/Input/input.stories.tsx +150 -0
- package/src/components/Input/input.tsx +102 -45
- package/src/components/LinkInput/index.ts +5 -0
- package/src/components/LinkInput/link-input.anatomy.stories.tsx +748 -0
- package/src/components/LinkInput/link-input.principles.stories.tsx +182 -0
- package/src/components/LinkInput/link-input.spec.md +25 -7
- package/src/components/LinkInput/link-input.stories.tsx +133 -0
- package/src/components/LinkInput/link-input.tsx +89 -66
- package/src/components/Menu/index.ts +5 -0
- package/src/components/Menu/menu-item.anatomy.stories.tsx +780 -0
- package/src/components/Menu/menu-item.principles.stories.tsx +81 -0
- package/src/components/Menu/menu-item.spec.md +55 -32
- package/src/components/Menu/menu-item.stories.tsx +242 -0
- package/src/components/Menu/menu-item.tsx +71 -12
- package/src/components/Notice/index.ts +5 -0
- package/src/components/Notice/notice.anatomy.stories.tsx +614 -0
- package/src/components/Notice/notice.principles.stories.tsx +102 -0
- package/src/components/Notice/notice.spec.md +39 -18
- package/src/components/Notice/notice.stories.tsx +257 -0
- package/src/components/Notice/notice.tsx +62 -16
- package/src/components/NumberInput/index.ts +5 -0
- package/src/components/NumberInput/number-input.anatomy.stories.tsx +822 -0
- package/src/components/NumberInput/number-input.principles.stories.tsx +206 -0
- package/src/components/NumberInput/number-input.spec.md +35 -16
- package/src/components/NumberInput/number-input.stories.tsx +167 -0
- package/src/components/NumberInput/number-input.tsx +65 -31
- package/src/components/OverflowIndicator/index.ts +5 -0
- package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +550 -0
- package/src/components/OverflowIndicator/overflow-indicator.principles.stories.tsx +76 -0
- package/src/components/OverflowIndicator/overflow-indicator.spec.md +36 -26
- package/src/components/OverflowIndicator/overflow-indicator.stories.tsx +222 -0
- package/src/components/OverflowIndicator/overflow-indicator.tsx +22 -7
- package/src/components/Pagination/index.ts +5 -0
- package/src/components/Pagination/pagination.anatomy.stories.tsx +144 -0
- package/src/components/Pagination/pagination.principles.stories.tsx +81 -0
- package/src/components/Pagination/pagination.spec.md +150 -0
- package/src/components/Pagination/pagination.stories.tsx +97 -0
- package/src/components/Pagination/pagination.tsx +263 -0
- package/src/components/PeoplePicker/avatar-stack-overflow.ts +1 -1
- package/src/components/PeoplePicker/index.ts +5 -0
- package/src/components/PeoplePicker/people-picker-helpers.ts +5 -5
- package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +437 -0
- package/src/components/PeoplePicker/people-picker.principles.stories.tsx +196 -0
- package/src/components/PeoplePicker/people-picker.spec.md +97 -40
- package/src/components/PeoplePicker/people-picker.stories.tsx +245 -0
- package/src/components/PeoplePicker/people-picker.tsx +225 -108
- package/src/components/PeoplePicker/person-display.tsx +83 -47
- package/src/components/Popover/index.ts +5 -0
- package/src/components/Popover/popover.anatomy.stories.tsx +282 -0
- package/src/components/Popover/popover.principles.stories.tsx +316 -0
- package/src/components/Popover/popover.spec.md +41 -31
- package/src/components/Popover/popover.stories.tsx +120 -0
- package/src/components/Popover/popover.tsx +106 -47
- package/src/components/ProfileCard/index.ts +5 -0
- package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +493 -0
- package/src/components/ProfileCard/profile-card.principles.stories.tsx +225 -0
- package/src/components/ProfileCard/profile-card.spec.md +189 -0
- package/src/components/ProfileCard/profile-card.stories.tsx +57 -0
- package/src/components/{NameCard/name-card.tsx → ProfileCard/profile-card.tsx} +66 -39
- package/src/components/ProgressBar/index.ts +5 -0
- package/src/components/ProgressBar/progress-bar.anatomy.stories.tsx +441 -0
- package/src/components/ProgressBar/progress-bar.principles.stories.tsx +326 -0
- package/src/components/ProgressBar/progress-bar.spec.md +25 -21
- package/src/components/ProgressBar/progress-bar.stories.tsx +133 -0
- package/src/components/ProgressBar/progress-bar.tsx +6 -23
- package/src/components/README.md +5 -4
- package/src/components/RadioGroup/index.ts +5 -0
- package/src/components/RadioGroup/radio-group.anatomy.stories.tsx +672 -0
- package/src/components/RadioGroup/radio-group.principles.stories.tsx +163 -0
- package/src/components/RadioGroup/radio-group.spec.md +15 -9
- package/src/components/RadioGroup/radio-group.stories.tsx +204 -0
- package/src/components/RadioGroup/radio-group.tsx +219 -67
- package/src/components/Rating/index.ts +5 -0
- package/src/components/Rating/rating.anatomy.stories.tsx +498 -0
- package/src/components/Rating/rating.principles.stories.tsx +296 -0
- package/src/components/Rating/rating.spec.md +41 -29
- package/src/components/Rating/rating.stories.tsx +119 -0
- package/src/components/Rating/rating.tsx +37 -18
- package/src/components/ScrollArea/index.ts +5 -0
- package/src/components/ScrollArea/scroll-area.anatomy.stories.tsx +451 -0
- package/src/components/ScrollArea/scroll-area.principles.stories.tsx +331 -0
- package/src/components/ScrollArea/scroll-area.spec.md +24 -22
- package/src/components/ScrollArea/scroll-area.stories.tsx +247 -0
- package/src/components/ScrollArea/scroll-area.tsx +24 -7
- package/src/components/SegmentedControl/index.ts +5 -0
- package/src/components/SegmentedControl/segmented-control.anatomy.stories.tsx +295 -0
- package/src/components/SegmentedControl/segmented-control.principles.stories.tsx +284 -0
- package/src/components/SegmentedControl/segmented-control.spec.md +38 -39
- package/src/components/SegmentedControl/segmented-control.stories.tsx +37 -0
- package/src/components/SegmentedControl/segmented-control.tsx +44 -24
- package/src/components/Select/index.ts +5 -0
- package/src/components/Select/select.anatomy.stories.tsx +834 -0
- package/src/components/Select/select.principles.stories.tsx +312 -0
- package/src/components/Select/select.spec.md +99 -39
- package/src/components/Select/select.stories.tsx +273 -0
- package/src/components/Select/select.tsx +389 -97
- package/src/components/SelectMenu/index.ts +5 -0
- package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +830 -0
- package/src/components/SelectMenu/select-menu.principles.stories.tsx +106 -0
- package/src/components/SelectMenu/select-menu.spec.md +51 -32
- package/src/components/SelectMenu/select-menu.stories.tsx +143 -0
- package/src/components/SelectMenu/select-menu.tsx +276 -33
- package/src/components/SelectionControl/boolean-value.tsx +39 -0
- package/src/components/SelectionControl/index.ts +6 -0
- package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +580 -0
- package/src/components/SelectionControl/selection-item.principles.stories.tsx +77 -0
- package/src/components/SelectionControl/selection-item.spec.md +30 -16
- package/src/components/SelectionControl/selection-item.stories.tsx +137 -0
- package/src/components/SelectionControl/selection-item.tsx +8 -2
- package/src/components/Separator/index.ts +5 -0
- package/src/components/Separator/separator.anatomy.stories.tsx +125 -0
- package/src/components/Separator/separator.principles.stories.tsx +141 -0
- package/src/components/Separator/separator.spec.md +15 -9
- package/src/components/Separator/separator.stories.tsx +106 -0
- package/src/components/Separator/separator.tsx +2 -2
- package/src/components/Sheet/index.ts +5 -0
- package/src/components/Sheet/sheet.anatomy.stories.tsx +269 -0
- package/src/components/Sheet/sheet.principles.stories.tsx +353 -0
- package/src/components/Sheet/sheet.spec.md +16 -17
- package/src/components/Sheet/sheet.stories.tsx +172 -0
- package/src/components/Sheet/sheet.tsx +24 -14
- package/src/components/Sidebar/index.ts +5 -0
- package/src/components/Sidebar/sidebar.anatomy.stories.tsx +776 -0
- package/src/components/Sidebar/sidebar.principles.stories.tsx +507 -0
- package/src/components/Sidebar/sidebar.spec.md +65 -62
- package/src/components/Sidebar/sidebar.stories.tsx +535 -0
- package/src/components/Sidebar/sidebar.tsx +134 -57
- package/src/components/Skeleton/index.ts +5 -0
- package/src/components/Skeleton/skeleton.anatomy.stories.tsx +145 -0
- package/src/components/Skeleton/skeleton.principles.stories.tsx +234 -0
- package/src/components/Skeleton/skeleton.spec.md +15 -14
- package/src/components/Skeleton/skeleton.stories.tsx +134 -0
- package/src/components/Skeleton/skeleton.tsx +2 -2
- package/src/components/Slider/index.ts +5 -0
- package/src/components/Slider/slider.anatomy.stories.tsx +237 -0
- package/src/components/Slider/slider.principles.stories.tsx +250 -0
- package/src/components/Slider/slider.spec.md +66 -118
- package/src/components/Slider/slider.stories.tsx +115 -0
- package/src/components/Slider/slider.tsx +83 -26
- package/src/components/Steps/index.ts +5 -0
- package/src/components/Steps/steps.anatomy.stories.tsx +448 -0
- package/src/components/Steps/steps.principles.stories.tsx +300 -0
- package/src/components/Steps/steps.spec.md +91 -74
- package/src/components/Steps/steps.stories.tsx +306 -0
- package/src/components/Steps/steps.tsx +99 -37
- package/src/components/Switch/index.ts +5 -0
- package/src/components/Switch/switch.anatomy.stories.tsx +227 -0
- package/src/components/Switch/switch.principles.stories.tsx +177 -0
- package/src/components/Switch/switch.spec.md +53 -24
- package/src/components/Switch/switch.stories.tsx +95 -0
- package/src/components/Switch/switch.tsx +176 -43
- package/src/components/Tabs/index.ts +5 -0
- package/src/components/Tabs/tabs-inline-action-geometry.ts +94 -0
- package/src/components/Tabs/tabs.anatomy.stories.tsx +424 -0
- package/src/components/Tabs/tabs.principles.stories.tsx +291 -0
- package/src/components/Tabs/tabs.spec.md +58 -69
- package/src/components/Tabs/tabs.stories.tsx +257 -0
- package/src/components/Tabs/tabs.tsx +262 -82
- package/src/components/Tag/index.ts +5 -0
- package/src/components/Tag/tag.anatomy.stories.tsx +667 -0
- package/src/components/Tag/tag.principles.stories.tsx +270 -0
- package/src/components/Tag/tag.spec.md +112 -100
- package/src/components/Tag/tag.stories.tsx +88 -0
- package/src/components/Tag/tag.tsx +120 -80
- package/src/components/Textarea/index.ts +5 -0
- package/src/components/Textarea/textarea.anatomy.stories.tsx +310 -0
- package/src/components/Textarea/textarea.principles.stories.tsx +192 -0
- package/src/components/Textarea/textarea.spec.md +35 -16
- package/src/components/Textarea/textarea.stories.tsx +57 -0
- package/src/components/Textarea/textarea.tsx +154 -83
- package/src/components/TimePicker/index.ts +5 -0
- package/src/components/TimePicker/time-columns.tsx +15 -3
- package/src/components/TimePicker/time-picker.anatomy.stories.tsx +283 -0
- package/src/components/TimePicker/time-picker.principles.stories.tsx +133 -0
- package/src/components/TimePicker/time-picker.spec.md +58 -52
- package/src/components/TimePicker/time-picker.stories.tsx +193 -0
- package/src/components/TimePicker/time-picker.tsx +119 -60
- package/src/components/Toast/index.ts +5 -0
- package/src/components/Toast/toast.anatomy.stories.tsx +328 -0
- package/src/components/Toast/toast.principles.stories.tsx +216 -0
- package/src/components/Toast/toast.spec.md +26 -16
- package/src/components/Toast/toast.stories.tsx +241 -0
- package/src/components/Toast/toast.tsx +101 -23
- package/src/components/Tooltip/index.ts +5 -0
- package/src/components/Tooltip/tooltip.anatomy.stories.tsx +422 -0
- package/src/components/Tooltip/tooltip.principles.stories.tsx +204 -0
- package/src/components/Tooltip/tooltip.spec.md +37 -23
- package/src/components/Tooltip/tooltip.stories.tsx +82 -0
- package/src/components/Tooltip/tooltip.tsx +58 -12
- package/src/components/TreeView/index.ts +5 -0
- package/src/components/TreeView/tree-view.anatomy.stories.tsx +436 -0
- package/src/components/TreeView/tree-view.principles.stories.tsx +223 -0
- package/src/components/TreeView/tree-view.spec.md +103 -54
- package/src/components/TreeView/tree-view.stories.tsx +365 -0
- package/src/components/TreeView/tree-view.tsx +501 -58
- package/src/hooks/use-controllable.ts +7 -2
- package/src/hooks/use-is-touch-device.ts +8 -1
- package/src/hooks/use-overflow-items.ts +10 -6
- package/src/hooks/use-truncated.ts +111 -0
- package/src/index.ts +648 -71
- package/src/lib/drag-visual.ts +2 -2
- package/src/lib/utils.ts +21 -1
- package/src/patterns/README.md +20 -16
- package/src/patterns/action-bar/action-bar.spec.md +63 -45
- package/src/patterns/action-bar/action-bar.stories.tsx +619 -0
- package/src/patterns/element-anatomy/element-anatomy.spec.md +17 -13
- package/src/patterns/element-anatomy/index.ts +6 -0
- package/src/patterns/element-anatomy/inline-action.spec.md +33 -22
- package/src/patterns/element-anatomy/item-anatomy.spec.md +108 -79
- package/src/patterns/element-anatomy/item-anatomy.stories.tsx +1648 -0
- package/src/patterns/element-anatomy/item-anatomy.tsx +147 -56
- package/src/patterns/element-anatomy/truncated-text.spec.md +68 -0
- package/src/patterns/element-anatomy/truncated-text.tsx +61 -0
- package/src/patterns/header-canonical/chrome-header.tsx +26 -6
- package/src/patterns/header-canonical/header-canonical.css +3 -4
- package/src/patterns/header-canonical/header-canonical.spec.md +96 -42
- package/src/patterns/header-canonical/header-canonical.stories.tsx +257 -0
- package/src/patterns/header-canonical/index.ts +5 -0
- package/src/patterns/horizontal-overflow/horizontal-overflow.spec.md +36 -15
- package/src/patterns/horizontal-overflow/horizontal-overflow.stories.tsx +139 -0
- package/src/patterns/horizontal-overflow/horizontal-overflow.tsx +1 -1
- package/src/patterns/horizontal-overflow/index.ts +5 -0
- package/src/patterns/overlay-surface/index.ts +5 -0
- package/src/patterns/overlay-surface/overlay-surface.spec.md +94 -58
- package/src/patterns/overlay-surface/overlay-surface.stories.tsx +132 -0
- package/src/patterns/overlay-surface/overlay-surface.tsx +37 -18
- package/src/patterns/resize-handle/index.ts +5 -0
- package/src/patterns/resize-handle/resize-handle.spec.md +22 -13
- package/src/patterns/resize-handle/resize-handle.stories.tsx +73 -0
- package/src/patterns/resize-handle/resize-handle.tsx +13 -18
- package/src/story-governance/category-matrix.json +132 -0
- package/src/story-governance/category-matrix.ts +55 -0
- package/src/styles/base.css +99 -0
- package/src/styles/tokens.css +14 -0
- package/src/tokens/README.md +10 -5
- package/src/tokens/categorical-color.ts +164 -0
- package/src/tokens/color/color.spec.md +95 -72
- package/src/tokens/color/color.stories.tsx +403 -0
- package/src/tokens/color/primitives.css +23 -7
- package/src/tokens/color/semantic.css +75 -21
- package/src/tokens/density/density.spec.md +33 -30
- package/src/tokens/density/density.stories.tsx +254 -0
- package/src/tokens/elevation/elevation.spec.md +11 -14
- package/src/tokens/elevation/elevation.stories.tsx +99 -0
- package/src/tokens/layoutSpace/layoutSpace.css +1 -1
- package/src/tokens/layoutSpace/layoutSpace.spec.md +44 -31
- package/src/tokens/layoutSpace/layoutSpace.stories.tsx +76 -0
- package/src/tokens/motion/motion.css +49 -20
- package/src/tokens/motion/motion.spec.md +45 -21
- package/src/tokens/motion/motion.stories.tsx +196 -0
- package/src/tokens/motion/motion.ts +8 -7
- package/src/tokens/motion/overlay-motion.ts +26 -0
- package/src/tokens/opacity/opacity.spec.md +15 -20
- package/src/tokens/opacity/opacity.stories.tsx +66 -0
- package/src/tokens/orphan-tokens.spec.md +22 -11
- package/src/tokens/radius/radius.spec.md +14 -11
- package/src/tokens/radius/radius.stories.tsx +87 -0
- package/src/tokens/token-system.spec.md +241 -0
- package/src/tokens/typography/typography.css +1 -1
- package/src/tokens/typography/typography.spec.md +12 -8
- package/src/tokens/typography/typography.stories.tsx +134 -0
- package/src/tokens/uiSize/icon-size.ts +3 -4
- package/src/tokens/uiSize/uiSize.css +58 -1
- package/src/tokens/uiSize/uiSize.spec.md +61 -32
- package/src/tokens/uiSize/uiSize.stories.tsx +106 -0
- package/src/tokens/utility-registry.json +426 -0
- package/src/tokens/utility-registry.schema.json +409 -0
- package/tools/README.md +9 -0
- package/tools/codemods/cli.mjs +93 -0
- package/tools/codemods/index.mjs +582 -0
- package/tools/shared/safe-filesystem.mjs +371 -0
- package/tools/visual-baseline/cli.mjs +97 -0
- package/tools/visual-baseline/index.mjs +522 -0
- package/dist/components/NameCard/name-card.d.ts.map +0 -1
- package/dist/components/NameCard/name-card.js.map +0 -1
- package/ds-canonical/commands/gov-status.md +0 -79
- package/ds-canonical/hooks/check_app_shell_primary_header_consistency.sh +0 -68
- package/ds-canonical/hooks/check_chrome_header_handcraft.sh +0 -70
- package/ds-canonical/hooks/check_header_with_tabs_border.sh +0 -87
- package/ds-canonical/hooks/check_tab_lg_chrome_header_equal.sh +0 -66
- package/ds-canonical/hooks/tests/KNOWN-BROKEN.md +0 -15
- package/ds-canonical/hooks/tests/test_check_story_category.sh.broken +0 -187
- package/ds-canonical/references/audit-coverage-vs-24-checklist.md +0 -74
- package/ds-canonical/skills/design-system-audit/references/spec-template.md +0 -66
- package/src/components/NameCard/name-card.spec.md +0 -171
|
@@ -0,0 +1,2454 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// refresh-fork-launchers.mjs — C-prime 接線骨架刷新(sync-all 呼叫 + harness 可測)
|
|
3
|
+
//
|
|
4
|
+
// 既有 fork 的「接線骨架」(provider configs + governance/bin 的 manifest-declared shared launchers)原本
|
|
5
|
+
// 不隨 npm 同步 → DS 改接線層後 user fork 拿不到(2026-06-17 user 抓:完全同步不偏移未達成)。
|
|
6
|
+
// 本模組從 npm-current 的 ds-canonical/fork/launchers/ 把骨架 idempotent 刷新進 fork:
|
|
7
|
+
// 1. copy manifest-declared launcher set → manifest.consumer.launcherDestination(官方控管,唯一共享副本)
|
|
8
|
+
// 2. merge settings-hooks.json 進 .claude/settings.json:
|
|
9
|
+
// - strip 所有 event 裡「引用 canonical launcher set」的舊註冊(去重)
|
|
10
|
+
// - append canonical 啟動器註冊
|
|
11
|
+
// - retire only the exact unsafe allows shipped by older corpus versions
|
|
12
|
+
// - replace policy-owned permissions.ask, retain non-conflicting consumer allow/deny, and enforce safe modes
|
|
13
|
+
// → 不 clobber user 自有「非治理」hook;重跑結果一致(idempotent)
|
|
14
|
+
// 4. provider surfaces:manifest-declared instructions + hooks + skills + closed trees 送達 fork root
|
|
15
|
+
// — AGENTS.md 與 provider wiring 是 immutable governance views,固定 clobber;product 規則只放 governance/overlay.md。
|
|
16
|
+
// 安全:.github/no-governance-sync opt-out → 整段 skip;launchers 未 ship(舊 npm)→ skip 不報錯。
|
|
17
|
+
// §15 契約:deterministic(輸出只依 corpus+dest 內容)/ idempotent(重跑收斂)/ atomic(所有檔案都走
|
|
18
|
+
// same-directory tmp+rename,不沿既有 target inode 覆寫)/ dry-run(opts.dryRun 只算不寫)/ json(CLI --json)。
|
|
19
|
+
//
|
|
20
|
+
// 抽成獨立模組 = sync-all 呼叫 + test-fork-governance.mjs 直接測(不需真跑 npm install)。
|
|
21
|
+
|
|
22
|
+
import { closeSync, existsSync, fchmodSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, cpSync, writeFileSync } from 'node:fs'
|
|
23
|
+
import { createHash, randomUUID } from 'node:crypto'
|
|
24
|
+
import { join, dirname, resolve, sep, isAbsolute, relative } from 'node:path'
|
|
25
|
+
import { fileURLToPath } from 'node:url'
|
|
26
|
+
import {
|
|
27
|
+
compareUtf8Bytes,
|
|
28
|
+
portableRepositoryPathKey,
|
|
29
|
+
repositoryPathsOverlap,
|
|
30
|
+
validateAuthenticatedProviderLifecycle,
|
|
31
|
+
} from './lib/provider-lifecycle.mjs'
|
|
32
|
+
import { assertNoRootNpmShrinkwrap } from './lib/governance-dependency-bootstrap.mjs'
|
|
33
|
+
|
|
34
|
+
// Consumer upgrades run repository-owned code against attacker-editable working trees. Lexical
|
|
35
|
+
// containment alone is insufficient: an in-repo parent symlink can redirect copy/rename/remove to
|
|
36
|
+
// a sibling or arbitrary host path. These helpers are the single path-safety SSOT shared with
|
|
37
|
+
// sync-all.mjs; every managed source/target is validated before the first mutation.
|
|
38
|
+
export function canonicalRepositoryRoot(root) {
|
|
39
|
+
const absolute = resolve(root)
|
|
40
|
+
const info = lstatSync(absolute)
|
|
41
|
+
if (!info.isDirectory() || info.isSymbolicLink()) throw new Error(`repository root must be a real directory:${absolute}`)
|
|
42
|
+
return realpathSync(absolute)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function normalizeRepositoryRelative(value, label = 'path', { allowGit = false } = {}) {
|
|
46
|
+
if (typeof value !== 'string' || !value || value.includes('\0') || isAbsolute(value)) throw new Error(`${label} must be a non-empty repository-relative path`)
|
|
47
|
+
const normalized = value.replaceAll('\\', '/').replace(/^\.\//, '').replace(/\/+$/, '')
|
|
48
|
+
const parts = normalized.split('/')
|
|
49
|
+
if (!normalized || parts.some((part) => !part || part === '.' || part === '..')) throw new Error(`${label} escapes its repository root:${value}`)
|
|
50
|
+
if (!allowGit && parts[0] === '.git') throw new Error(`${label} may not address .git:${value}`)
|
|
51
|
+
return normalized
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function pathEntryExists(path) {
|
|
55
|
+
try { lstatSync(path); return true }
|
|
56
|
+
catch (error) {
|
|
57
|
+
if (error?.code === 'ENOENT') return false
|
|
58
|
+
throw error
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Linux permits entries that alias after the case-folding/Unicode normalization used by default
|
|
63
|
+
// macOS filesystems. Detect those aliases without following symlinks so a newly claimed provider
|
|
64
|
+
// surface cannot create a repository that is uncheckoutable or clobber-prone on another host.
|
|
65
|
+
function existingPortablePathCollision(root, repositoryPath) {
|
|
66
|
+
const canonicalRoot = canonicalRepositoryRoot(root)
|
|
67
|
+
const normalized = normalizeRepositoryRelative(repositoryPath, 'portable collision path')
|
|
68
|
+
let cursor = canonicalRoot
|
|
69
|
+
const segments = normalized.split('/')
|
|
70
|
+
for (const [index, segment] of segments.entries()) {
|
|
71
|
+
const cursorInfo = lstatSync(cursor)
|
|
72
|
+
if (!cursorInfo.isDirectory() || cursorInfo.isSymbolicLink()) return cursor
|
|
73
|
+
const matches = readdirSync(cursor)
|
|
74
|
+
.filter((name) => portableRepositoryPathKey(name) === portableRepositoryPathKey(segment))
|
|
75
|
+
.sort()
|
|
76
|
+
if (!matches.length) return null
|
|
77
|
+
const matched = matches[0]
|
|
78
|
+
const child = join(cursor, matched)
|
|
79
|
+
if (matches.length > 1 || matched !== segment || index === segments.length - 1) return child
|
|
80
|
+
const childInfo = lstatSync(child)
|
|
81
|
+
if (!childInfo.isDirectory() || childInfo.isSymbolicLink()) return child
|
|
82
|
+
cursor = child
|
|
83
|
+
}
|
|
84
|
+
return null
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function assertNoSymlinkPath(root, target, label = 'path', { allowMissing = true } = {}) {
|
|
88
|
+
const canonicalRoot = canonicalRepositoryRoot(root)
|
|
89
|
+
const absolute = resolve(target)
|
|
90
|
+
const rel = relative(canonicalRoot, absolute)
|
|
91
|
+
if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) throw new Error(`${label} escapes repository root:${absolute}`)
|
|
92
|
+
let cursor = canonicalRoot
|
|
93
|
+
for (const part of rel.split(sep).filter(Boolean)) {
|
|
94
|
+
cursor = join(cursor, part)
|
|
95
|
+
if (!pathEntryExists(cursor)) {
|
|
96
|
+
if (allowMissing) return absolute
|
|
97
|
+
throw new Error(`${label} is missing:${cursor}`)
|
|
98
|
+
}
|
|
99
|
+
const info = lstatSync(cursor)
|
|
100
|
+
if (info.isSymbolicLink()) throw new Error(`${label} contains symlink component:${cursor}`)
|
|
101
|
+
}
|
|
102
|
+
return absolute
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function assertSafeTree(root, target, label = 'tree', { allowMissing = false, allowInternalSymlinks = false } = {}) {
|
|
106
|
+
const absolute = assertNoSymlinkPath(root, target, label, { allowMissing })
|
|
107
|
+
if (!pathEntryExists(absolute)) return absolute
|
|
108
|
+
const visit = (path) => {
|
|
109
|
+
const info = lstatSync(path)
|
|
110
|
+
if (info.isSymbolicLink() && !allowInternalSymlinks) throw new Error(`${label} contains symlink:${path}`)
|
|
111
|
+
if (info.isDirectory()) for (const name of readdirSync(path)) visit(join(path, name))
|
|
112
|
+
else if (info.isFile() && info.nlink !== 1) throw new Error(`${label} contains hard-link alias:${path}`)
|
|
113
|
+
else if (!info.isFile() && !info.isSymbolicLink()) throw new Error(`${label} contains unsupported file type:${path}`)
|
|
114
|
+
}
|
|
115
|
+
visit(absolute)
|
|
116
|
+
return absolute
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// One same-directory, attempt-unique replacement primitive is the managed-file durability SSOT.
|
|
120
|
+
// Keeping it separately testable lets the concurrency test exercise the exact production writer
|
|
121
|
+
// without launching two whole provider-tree transactions against the same repository.
|
|
122
|
+
export function atomicReplaceRepositoryFile(projectDir, target, content, { mode = 0o600, label = 'atomic repository file' } = {}) {
|
|
123
|
+
projectDir = canonicalRepositoryRoot(projectDir)
|
|
124
|
+
target = resolve(target)
|
|
125
|
+
assertNoSymlinkPath(projectDir, target, label)
|
|
126
|
+
mkdirSync(dirname(target), { recursive: true })
|
|
127
|
+
assertNoSymlinkPath(projectDir, dirname(target), `${label} parent`, { allowMissing: false })
|
|
128
|
+
const tmpPath = `${target}.tmp-${process.pid}-${randomUUID()}`
|
|
129
|
+
assertNoSymlinkPath(projectDir, tmpPath, `${label} temporary path`)
|
|
130
|
+
let descriptor = null
|
|
131
|
+
let parentDescriptor = null
|
|
132
|
+
try {
|
|
133
|
+
descriptor = openSync(tmpPath, 'wx', 0o600)
|
|
134
|
+
writeFileSync(descriptor, content)
|
|
135
|
+
fchmodSync(descriptor, mode & 0o777)
|
|
136
|
+
fsyncSync(descriptor)
|
|
137
|
+
closeSync(descriptor)
|
|
138
|
+
descriptor = null
|
|
139
|
+
assertNoSymlinkPath(projectDir, dirname(target), `${label} parent before replace`, { allowMissing: false })
|
|
140
|
+
const temporaryInfo = lstatSync(tmpPath)
|
|
141
|
+
if (!temporaryInfo.isFile() || temporaryInfo.isSymbolicLink() || temporaryInfo.nlink !== 1) {
|
|
142
|
+
throw new Error(`${label} temporary path is not one regular unaliased file:${tmpPath}`)
|
|
143
|
+
}
|
|
144
|
+
renameSync(tmpPath, target)
|
|
145
|
+
parentDescriptor = openSync(dirname(target), 'r')
|
|
146
|
+
fsyncSync(parentDescriptor)
|
|
147
|
+
closeSync(parentDescriptor)
|
|
148
|
+
parentDescriptor = null
|
|
149
|
+
} finally {
|
|
150
|
+
if (descriptor !== null) closeSync(descriptor)
|
|
151
|
+
if (parentDescriptor !== null) closeSync(parentDescriptor)
|
|
152
|
+
if (pathEntryExists(tmpPath)) rmSync(tmpPath, { force: true })
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Required npm commands are part of the immutable consumer BOM, but package.json also contains
|
|
157
|
+
// product-owned commands. Merge only the authenticated names so sync-all can add/upgrade governance
|
|
158
|
+
// entrypoints without replacing unrelated product scripts or teaching an old updater each new name.
|
|
159
|
+
export function mergeRequiredPackageScripts(manifest, requiredScripts) {
|
|
160
|
+
if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) throw new Error('consumer package manifest must be an object')
|
|
161
|
+
if (!requiredScripts || typeof requiredScripts !== 'object' || Array.isArray(requiredScripts)) throw new Error('consumer BOM requiredScripts must be an object')
|
|
162
|
+
if (manifest.scripts !== undefined && (!manifest.scripts || typeof manifest.scripts !== 'object' || Array.isArray(manifest.scripts))) {
|
|
163
|
+
throw new Error('consumer package scripts must be an object')
|
|
164
|
+
}
|
|
165
|
+
const entries = Object.entries(requiredScripts)
|
|
166
|
+
const names = entries.map(([name]) => name)
|
|
167
|
+
if (!entries.length || JSON.stringify(names) !== JSON.stringify([...names].sort())) {
|
|
168
|
+
throw new Error('consumer BOM requiredScripts must be nonempty and sorted')
|
|
169
|
+
}
|
|
170
|
+
for (const [name, command] of entries) {
|
|
171
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9:_-]*$/.test(name) || typeof command !== 'string' || !command.trim() || /[\0\r\n]/.test(command)) {
|
|
172
|
+
throw new Error(`consumer BOM required script is invalid:${name}`)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
...manifest,
|
|
177
|
+
scripts: {
|
|
178
|
+
...(manifest.scripts || {}),
|
|
179
|
+
...Object.fromEntries(entries),
|
|
180
|
+
},
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function safeCopyFile({ sourceRoot, source, targetRoot, target, label, mode = null }) {
|
|
185
|
+
assertNoSymlinkPath(sourceRoot, source, `${label} source`, { allowMissing: false })
|
|
186
|
+
const sourceInfo = lstatSync(source)
|
|
187
|
+
if (!sourceInfo.isFile() || sourceInfo.isSymbolicLink() || sourceInfo.nlink !== 1) throw new Error(`${label} source is not one regular unaliased file:${source}`)
|
|
188
|
+
assertNoSymlinkPath(targetRoot, target, `${label} target`)
|
|
189
|
+
atomicReplaceRepositoryFile(targetRoot, target, readFileSync(source), {
|
|
190
|
+
mode: mode === null ? sourceInfo.mode & 0o777 : mode,
|
|
191
|
+
label: `${label} target`,
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function safeReplaceTree({ sourceRoot, source, targetRoot, target, label }) {
|
|
196
|
+
assertSafeTree(sourceRoot, source, `${label} source`)
|
|
197
|
+
assertNoSymlinkPath(targetRoot, target, `${label} target`)
|
|
198
|
+
if (pathEntryExists(target)) rmSync(target, { recursive: true, force: true })
|
|
199
|
+
mkdirSync(dirname(target), { recursive: true })
|
|
200
|
+
assertNoSymlinkPath(targetRoot, dirname(target), `${label} target parent`, { allowMissing: false })
|
|
201
|
+
cpSync(source, target, { recursive: true })
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Claude Code 的 .claude/settings.json 允許 JSONC(// 行註解 / block 註解)→ JSON.parse 會炸。
|
|
205
|
+
// string-aware strip 註解後再 parse(fork user 若註解過 settings,skeleton 刷新才不會默默 no-op)。
|
|
206
|
+
function parseJsonc(text, label) {
|
|
207
|
+
let out = '', inStr = false, inLine = false, inBlock = false
|
|
208
|
+
for (let i = 0; i < text.length; i++) {
|
|
209
|
+
const c = text[i], n = text[i + 1]
|
|
210
|
+
if (inLine) { if (c === '\n') { inLine = false; out += c } continue }
|
|
211
|
+
if (inBlock) { if (c === '*' && n === '/') { inBlock = false; i++ } continue }
|
|
212
|
+
if (inStr) { out += c; if (c === '\\') out += text[++i] ?? ''; else if (c === '"') inStr = false; continue }
|
|
213
|
+
if (c === '"') { inStr = true; out += c; continue }
|
|
214
|
+
if (c === '/' && n === '/') { inLine = true; i++; continue }
|
|
215
|
+
if (c === '/' && n === '*') { inBlock = true; i++; continue }
|
|
216
|
+
out += c
|
|
217
|
+
}
|
|
218
|
+
try { return JSON.parse(out) } catch (e) { throw new Error(`${label} JSONC strip 後仍非法 JSON:${e.message}`) }
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const LAUNCHERS = ['fork-governance-dispatcher.sh', 'inject_fork_governance_preamble.sh']
|
|
222
|
+
const CLAUDE_PERMISSION_POLICY_SOURCE = 'packages/design-system/ds-canonical/adapters/claude-settings-base.json'
|
|
223
|
+
const escRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
224
|
+
// path-segment 比對:啟動器必以 `/<name>` 出現且後接邊界(引號/空白/結尾)。
|
|
225
|
+
// 避免 loose substring 誤刪「command 只是『含』啟動器名為子字串」的 user hook(adversarial FINDING 2b)。
|
|
226
|
+
const refsLauncher = (cmd) => LAUNCHERS.some((l) => new RegExp(`/${escRe(l)}(?=["'\\s]|$)`).test(cmd || ''))
|
|
227
|
+
const refsManagedRepositoryPath = (cmd, path) => {
|
|
228
|
+
const variants = [path, `./${path}`, `$CLAUDE_PROJECT_DIR/${path}`, `\${CLAUDE_PROJECT_DIR}/${path}`]
|
|
229
|
+
return variants.some((candidate) => new RegExp(`(?:^|["'\\s])${escRe(candidate)}(?=["'\\s]|$)`).test(cmd || ''))
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Pure materializer for one validated merge-hook-map-into-base-v1 surface. The caller owns file
|
|
234
|
+
* authentication and atomic replacement; this function deliberately selects behavior from the
|
|
235
|
+
* registered materializer/base-template engines, never from a provider id.
|
|
236
|
+
*/
|
|
237
|
+
export function mergeProviderHookSettings({ settings, canonicalConfig, policy, label = 'provider hook settings' } = {}) {
|
|
238
|
+
if (!settings || typeof settings !== 'object' || Array.isArray(settings)) throw new Error(`${label} must be an object`)
|
|
239
|
+
if (!canonicalConfig || typeof canonicalConfig !== 'object' || Array.isArray(canonicalConfig)) throw new Error(`${label} canonical config must be an object`)
|
|
240
|
+
if (
|
|
241
|
+
policy?.materializer?.engine !== 'json-hook-map-v1'
|
|
242
|
+
|| policy.materializer.mergeStrategy !== 'merge-hook-map-into-base-v1'
|
|
243
|
+
|| typeof policy.materializer.hooksField !== 'string'
|
|
244
|
+
|| !policy.baseTemplate
|
|
245
|
+
) throw new Error(`${label} has no supported registered merge materializer`)
|
|
246
|
+
const merged = structuredClone(settings)
|
|
247
|
+
const hooksField = policy.materializer.hooksField
|
|
248
|
+
const priorHooks = merged[hooksField]
|
|
249
|
+
if (priorHooks !== undefined && (!priorHooks || typeof priorHooks !== 'object' || Array.isArray(priorHooks))) {
|
|
250
|
+
throw new Error(`${label} ${hooksField} must be an object`)
|
|
251
|
+
}
|
|
252
|
+
const retiredIds = []
|
|
253
|
+
// F6 fail-closed:canonical hook map 必須存在、非空,且每 event 都是非空合形的 group 陣列。
|
|
254
|
+
// 寬鬆 fallback(`|| {}` / `Array.isArray ? : []`)會把「看不懂的另一世代 artifact 形狀」當
|
|
255
|
+
// 「零 hooks」:下方 strip(canonicalCommands / refsLauncher)照跑、append 卻是空 → 靜默清空
|
|
256
|
+
// 治理 hooks(WM 1f71fec 同型事故)。禁止。
|
|
257
|
+
const canonicalHookMap = canonicalConfig[hooksField]
|
|
258
|
+
if (!canonicalHookMap || typeof canonicalHookMap !== 'object' || Array.isArray(canonicalHookMap) || !Object.keys(canonicalHookMap).length) {
|
|
259
|
+
throw new Error(
|
|
260
|
+
`${label}: canonical hook artifact carries no non-empty ${JSON.stringify(hooksField)} map — the installed corpus uses a schema generation this scripts/refresh-fork-launchers.mjs does not understand; `
|
|
261
|
+
+ 'refusing to write (an empty merge would silently strip every governance hook). '
|
|
262
|
+
+ 'Migrate the fork scaffold to the matching generation first (npm run sync-all -- --apply --to <exact-version>).'
|
|
263
|
+
)
|
|
264
|
+
}
|
|
265
|
+
const canonicalCommands = new Set()
|
|
266
|
+
for (const [event, groups] of Object.entries(canonicalHookMap)) {
|
|
267
|
+
if (!Array.isArray(groups) || !groups.length) throw new Error(`${label} canonical ${hooksField}.${event} must be a non-empty array`)
|
|
268
|
+
for (const group of groups) {
|
|
269
|
+
if (!group || typeof group !== 'object' || Array.isArray(group) || !Array.isArray(group.hooks) || !group.hooks.length) {
|
|
270
|
+
throw new Error(`${label} canonical ${hooksField}.${event} contains an invalid or empty hook group`)
|
|
271
|
+
}
|
|
272
|
+
for (const handler of group.hooks) {
|
|
273
|
+
if (typeof handler?.command !== 'string' || !handler.command) throw new Error(`${label} canonical ${hooksField}.${event} contains a hook without a command`)
|
|
274
|
+
canonicalCommands.add(handler.command)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const retainedHooks = {}
|
|
279
|
+
for (const groups of Object.values(priorHooks || {})) for (const group of Array.isArray(groups) ? groups : []) {
|
|
280
|
+
for (const handler of Array.isArray(group?.hooks) ? group.hooks : []) {
|
|
281
|
+
const retired = policy.retiredHookRegistrations.find((record) => refsManagedRepositoryPath(handler?.command, record.path))
|
|
282
|
+
if (retired && !retiredIds.includes(retired.id)) retiredIds.push(retired.id)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
for (const [event, groups] of Object.entries(priorHooks || {})) {
|
|
287
|
+
if (!Array.isArray(groups)) throw new Error(`${label} ${hooksField}.${event} must be an array`)
|
|
288
|
+
const retainedGroups = []
|
|
289
|
+
for (const group of groups) {
|
|
290
|
+
if (!group || typeof group !== 'object' || Array.isArray(group) || !Array.isArray(group.hooks)) {
|
|
291
|
+
throw new Error(`${label} ${hooksField}.${event} contains an invalid hook group`)
|
|
292
|
+
}
|
|
293
|
+
const hooks = group.hooks.filter((handler) => {
|
|
294
|
+
const command = handler?.command
|
|
295
|
+
if (typeof command !== 'string') return true
|
|
296
|
+
if (canonicalCommands.has(command) || refsLauncher(command)) return false
|
|
297
|
+
return !policy.retiredHookRegistrations.some((record) => refsManagedRepositoryPath(command, record.path))
|
|
298
|
+
})
|
|
299
|
+
if (hooks.length) retainedGroups.push({ ...structuredClone(group), hooks: structuredClone(hooks) })
|
|
300
|
+
}
|
|
301
|
+
if (retainedGroups.length) retainedHooks[event] = retainedGroups
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Remove only previously managed/retired registrations, retain consumer-owned hooks, then append
|
|
305
|
+
// the authenticated canonical groups exactly once. This keeps refresh idempotent without turning
|
|
306
|
+
// a provider adapter update into authority over unrelated product automation.
|
|
307
|
+
merged[hooksField] = retainedHooks
|
|
308
|
+
for (const [event, groups] of Object.entries(canonicalHookMap)) {
|
|
309
|
+
merged[hooksField][event] = [...(merged[hooksField][event] || []), ...structuredClone(groups)]
|
|
310
|
+
}
|
|
311
|
+
// Never-empty postcondition(F6 迴歸絆網):merge 結果必含每個 canonical event 的註冊,
|
|
312
|
+
// 否則代表上游 shape 判定失守 → 寧可 throw 也不寫出被清空的 hook surface。
|
|
313
|
+
for (const event of Object.keys(canonicalHookMap)) {
|
|
314
|
+
if (!Array.isArray(merged[hooksField][event]) || !merged[hooksField][event].length) {
|
|
315
|
+
throw new Error(`${label}: merged ${hooksField}.${event} lost its canonical registrations — refusing to write an emptied hook surface`)
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
for (const [key, value] of Object.entries(canonicalConfig)) {
|
|
319
|
+
if (key === hooksField || key === 'permissions' || key === 'disableAutoMode') continue
|
|
320
|
+
merged[key] = structuredClone(value)
|
|
321
|
+
}
|
|
322
|
+
if (policy.baseTemplate.engine === 'claude-permission-policy-v1') {
|
|
323
|
+
if (merged.permissions !== undefined && (!merged.permissions || typeof merged.permissions !== 'object' || Array.isArray(merged.permissions))) {
|
|
324
|
+
throw new Error(`${label} permissions must be an object`)
|
|
325
|
+
}
|
|
326
|
+
merged.permissions = merged.permissions || {}
|
|
327
|
+
for (const key of ['allow', 'ask', 'deny']) {
|
|
328
|
+
if (merged.permissions[key] !== undefined && !Array.isArray(merged.permissions[key])) throw new Error(`${label} permissions.${key} must be an array`)
|
|
329
|
+
if ((merged.permissions[key] || []).some((rule) => typeof rule !== 'string')) throw new Error(`${label} permissions.${key} must contain only strings`)
|
|
330
|
+
}
|
|
331
|
+
const canonicalPermissions = canonicalConfig.permissions
|
|
332
|
+
if (!canonicalPermissions || typeof canonicalPermissions !== 'object' || Array.isArray(canonicalPermissions)) {
|
|
333
|
+
throw new Error(`${label} canonical permission base is missing`)
|
|
334
|
+
}
|
|
335
|
+
const retiredAllow = new Set(policy.permissionPolicy.retiredPermissionAllow)
|
|
336
|
+
const nextDeny = Array.from(new Set([...(canonicalPermissions.deny || []), ...(merged.permissions.deny || [])]))
|
|
337
|
+
const nextDenySet = new Set(nextDeny)
|
|
338
|
+
// Human-only ask semantics are policy-owned. Retaining arbitrary historical consumer asks would
|
|
339
|
+
// let an old provider config reintroduce engineering approval milestones after the canonical
|
|
340
|
+
// Standing Authorization delegated them. Consumer-specific safety may remain a stricter deny;
|
|
341
|
+
// ask is always the authenticated canonical set.
|
|
342
|
+
const nextAsk = Array.from(new Set(canonicalPermissions.ask || []))
|
|
343
|
+
.filter((rule) => !nextDenySet.has(rule))
|
|
344
|
+
const nextAskSet = new Set(nextAsk)
|
|
345
|
+
merged.permissions.allow = Array.from(new Set([
|
|
346
|
+
...(merged.permissions.allow || []).filter((rule) => !retiredAllow.has(rule)),
|
|
347
|
+
...(canonicalPermissions.allow || []),
|
|
348
|
+
])).filter((rule) => !nextAskSet.has(rule) && !nextDenySet.has(rule))
|
|
349
|
+
merged.permissions.ask = nextAsk
|
|
350
|
+
merged.permissions.deny = nextDeny
|
|
351
|
+
merged.permissions.defaultMode = canonicalPermissions.defaultMode
|
|
352
|
+
merged.permissions.disableBypassPermissionsMode = canonicalPermissions.disableBypassPermissionsMode
|
|
353
|
+
if (Object.hasOwn(canonicalConfig, 'disableAutoMode')) throw new Error(`${label} canonical permission base carries the retired disableAutoMode flag`)
|
|
354
|
+
delete merged.disableAutoMode
|
|
355
|
+
delete merged.permissions.disableAutoMode
|
|
356
|
+
delete merged.defaultMode
|
|
357
|
+
} else if (policy.baseTemplate.engine === 'static-json-object-v1') {
|
|
358
|
+
if (policy.permissionPolicy !== null) throw new Error(`${label} static base template may not claim a permission policy`)
|
|
359
|
+
for (const [key, value] of Object.entries(canonicalConfig)) if (key !== hooksField) merged[key] = structuredClone(value)
|
|
360
|
+
} else {
|
|
361
|
+
throw new Error(`${label} base-template engine is unsupported:${policy.baseTemplate.engine}`)
|
|
362
|
+
}
|
|
363
|
+
return { settings: merged, retiredIds: retiredIds.sort() }
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// ── F6 fail-closed schema generation gate(WM commit 1f71fec 教訓)──
|
|
367
|
+
// 舊代 refresh 對新代 settings-hooks.json 用 `canonical.hooks || {}` 寬鬆 fallback,把「看不懂的
|
|
368
|
+
// 另一世代 schema」當「零 hooks」:strip 舊治理註冊照跑、append 卻是空 → 靜默清空
|
|
369
|
+
// .claude/settings.json hooks = Claude 端治理整條斷線。本 gate 是唯一 generation 判定 SSOT:
|
|
370
|
+
// kind + schemaVersion 必須 exact match 本腳本世代,否則丟含遷移指引的錯誤;絕不寫檔。
|
|
371
|
+
export const SETTINGS_HOOKS_KIND = 'provider-hook-merge-policies'
|
|
372
|
+
export const SETTINGS_HOOKS_SCHEMA_VERSION = 1
|
|
373
|
+
|
|
374
|
+
export function assertSupportedSettingsHooksGeneration(value, label = 'installed launchers/settings-hooks.json') {
|
|
375
|
+
const record = value && typeof value === 'object' && !Array.isArray(value) ? value : {}
|
|
376
|
+
if (record.kind === SETTINGS_HOOKS_KIND && record.schemaVersion === SETTINGS_HOOKS_SCHEMA_VERSION) return
|
|
377
|
+
throw new Error(
|
|
378
|
+
`${label} declares kind=${JSON.stringify(record.kind ?? null)} schemaVersion=${JSON.stringify(record.schemaVersion ?? null)}, `
|
|
379
|
+
+ `but this scripts/refresh-fork-launchers.mjs generation understands exactly ${SETTINGS_HOOKS_KIND}@${SETTINGS_HOOKS_SCHEMA_VERSION}. `
|
|
380
|
+
+ 'Refusing to merge provider hooks; nothing was written. Merging an ununderstood generation is exactly how an older scaffold silently emptied .claude/settings.json hooks. '
|
|
381
|
+
+ 'Migrate this fork scaffold to the generation matching the installed corpus first (npm run sync-all -- --apply --to <exact-version>), then re-run.'
|
|
382
|
+
)
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
export function validateCanonicalSettingsWiring(value, manifest) {
|
|
386
|
+
assertSupportedSettingsHooksGeneration(value)
|
|
387
|
+
exactKeys(value, ['_generated', 'kind', 'schemaVersion', 'sourceDigest', 'surfaces'], 'installed provider hook merge policies')
|
|
388
|
+
if (
|
|
389
|
+
value._generated !== 'build-fork-governance.mjs'
|
|
390
|
+
|| value.kind !== 'provider-hook-merge-policies'
|
|
391
|
+
|| value.schemaVersion !== 1
|
|
392
|
+
|| !SHA256.test(value.sourceDigest || '')
|
|
393
|
+
|| !value.surfaces
|
|
394
|
+
|| typeof value.surfaces !== 'object'
|
|
395
|
+
|| Array.isArray(value.surfaces)
|
|
396
|
+
) throw new Error('installed provider hook merge policy identity is invalid')
|
|
397
|
+
const expectedProviderIds = Object.entries(manifest?.providerSurfaces || {})
|
|
398
|
+
.filter(([, surface]) => surface.generated && surface.hookMaterializerId)
|
|
399
|
+
.filter(([providerId, surface]) => resolveManifestMaterializer(
|
|
400
|
+
manifest,
|
|
401
|
+
'hookViews',
|
|
402
|
+
surface.hookMaterializerId,
|
|
403
|
+
`${providerId}.hookMaterializerId`,
|
|
404
|
+
).mergeStrategy === 'merge-hook-map-into-base-v1')
|
|
405
|
+
.map(([providerId]) => providerId)
|
|
406
|
+
.sort()
|
|
407
|
+
if (JSON.stringify(Object.keys(value.surfaces).sort()) !== JSON.stringify(expectedProviderIds)) {
|
|
408
|
+
throw new Error('installed provider hook merge policies do not exactly match merge-capable provider surfaces')
|
|
409
|
+
}
|
|
410
|
+
const surfaces = {}
|
|
411
|
+
for (const providerId of expectedProviderIds) {
|
|
412
|
+
const record = value.surfaces[providerId]
|
|
413
|
+
exactKeys(record, [
|
|
414
|
+
'baseTemplateContract', 'hookArtifact', 'hookDestination', 'hookMaterializerId', 'mergeStrategy',
|
|
415
|
+
'permissionPolicy', 'retiredHookRegistrations', 'schemaVersion',
|
|
416
|
+
], `installed hook merge policy ${providerId}`)
|
|
417
|
+
const surface = manifest.providerSurfaces[providerId]
|
|
418
|
+
const materializer = resolveManifestMaterializer(manifest, 'hookViews', record.hookMaterializerId, `${providerId}.hookMaterializerId`)
|
|
419
|
+
const baseTemplate = resolveManifestMaterializer(manifest, 'hookBaseTemplateContracts', record.baseTemplateContract, `${providerId}.baseTemplateContract`)
|
|
420
|
+
if (
|
|
421
|
+
record.schemaVersion !== 1
|
|
422
|
+
|| record.hookArtifact !== surface.hookArtifact
|
|
423
|
+
|| record.hookDestination !== surface.hookDestination
|
|
424
|
+
|| record.hookMaterializerId !== surface.hookMaterializerId
|
|
425
|
+
|| record.mergeStrategy !== 'merge-hook-map-into-base-v1'
|
|
426
|
+
|| materializer.mergeStrategy !== record.mergeStrategy
|
|
427
|
+
|| materializer.baseTemplateContract !== record.baseTemplateContract
|
|
428
|
+
|| materializer.engine !== 'json-hook-map-v1'
|
|
429
|
+
) throw new Error(`${providerId}:installed hook merge policy differs from its provider materializer`)
|
|
430
|
+
if (!Array.isArray(record.retiredHookRegistrations)) throw new Error(`${providerId}:retired hook registration inventory is invalid`)
|
|
431
|
+
const retirements = record.retiredHookRegistrations.map((retirement, index) => {
|
|
432
|
+
exactKeys(retirement, ['id', 'path', 'provider', 'reasonCode'], `${providerId} retired hook registration[${index}]`)
|
|
433
|
+
const path = normalizeRepositoryRelative(retirement.path, `${providerId} retired hook registration[${index}].path`)
|
|
434
|
+
if (
|
|
435
|
+
retirement.provider !== providerId
|
|
436
|
+
|| !/^[a-z][a-z0-9-]*$/.test(retirement.id || '')
|
|
437
|
+
|| !/^[A-Z][A-Z0-9_]*$/.test(retirement.reasonCode || '')
|
|
438
|
+
) throw new Error(`${providerId}:retired hook registration[${index}] is invalid`)
|
|
439
|
+
return { ...retirement, path }
|
|
440
|
+
})
|
|
441
|
+
if (
|
|
442
|
+
JSON.stringify(retirements.map((item) => item.id)) !== JSON.stringify(retirements.map((item) => item.id).sort())
|
|
443
|
+
|| new Set(retirements.map((item) => portableRepositoryPathKey(item.path))).size !== retirements.length
|
|
444
|
+
) throw new Error(`${providerId}:retired hook registrations must be sorted by id and unique by path`)
|
|
445
|
+
if (baseTemplate.engine === 'claude-permission-policy-v1') {
|
|
446
|
+
exactKeys(record.permissionPolicy, ['retiredPermissionAllow', 'sha256', 'source'], `${providerId} permission policy`)
|
|
447
|
+
if (
|
|
448
|
+
record.permissionPolicy.source !== baseTemplate.source
|
|
449
|
+
|| !SHA256.test(record.permissionPolicy.sha256 || '')
|
|
450
|
+
|| !Array.isArray(record.permissionPolicy.retiredPermissionAllow)
|
|
451
|
+
|| record.permissionPolicy.retiredPermissionAllow.length === 0
|
|
452
|
+
|| record.permissionPolicy.retiredPermissionAllow.some((rule) => typeof rule !== 'string' || !/^Bash\(.+\)$/.test(rule))
|
|
453
|
+
) throw new Error(`${providerId}:permission policy binding is invalid`)
|
|
454
|
+
} else if (record.permissionPolicy !== null) throw new Error(`${providerId}:non-permission base template may not claim a permission policy`)
|
|
455
|
+
surfaces[providerId] = { ...record, retiredHookRegistrations: retirements, materializer, baseTemplate }
|
|
456
|
+
}
|
|
457
|
+
return { ...value, surfaces }
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const safeRelative = (root, value) => {
|
|
461
|
+
try {
|
|
462
|
+
const normalized = normalizeRepositoryRelative(value)
|
|
463
|
+
return resolve(root, normalized).startsWith(resolve(root) + sep)
|
|
464
|
+
} catch { return false }
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const SHA256 = /^[a-f0-9]{64}$/
|
|
468
|
+
const EXACT_SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z][0-9A-Za-z.-]*)?(?:\+[0-9A-Za-z.-]+)?$/
|
|
469
|
+
const stableVersionAtLeast = (value, minimum) => {
|
|
470
|
+
const parse = version => typeof version === 'string'
|
|
471
|
+
? version.match(/^(\d+)\.(\d+)\.(\d+)(?:\+[0-9A-Za-z.-]+)?$/)?.slice(1, 4).map(Number)
|
|
472
|
+
: null
|
|
473
|
+
const observed = parse(value)
|
|
474
|
+
const required = parse(minimum)
|
|
475
|
+
if (!observed || !required) return false
|
|
476
|
+
for (let index = 0; index < 3; index += 1) {
|
|
477
|
+
if (observed[index] !== required[index]) return observed[index] > required[index]
|
|
478
|
+
}
|
|
479
|
+
return true
|
|
480
|
+
}
|
|
481
|
+
const DISCOVERY_KEYS = Object.freeze([
|
|
482
|
+
'providerRootNames', 'instructionNames', 'instructionOverrideNames', 'configPaths', 'pluginRootNames',
|
|
483
|
+
])
|
|
484
|
+
const DISCOVERY_PATTERNS = Object.freeze({
|
|
485
|
+
providerRootNames: /^\.(?!git(?:hub)?$)[A-Za-z0-9][A-Za-z0-9_-]*$/,
|
|
486
|
+
instructionNames: /^[A-Za-z0-9][A-Za-z0-9_.@-]*\.md$/,
|
|
487
|
+
instructionOverrideNames: /^[A-Za-z0-9][A-Za-z0-9_.@-]*\.md$/,
|
|
488
|
+
configPaths: /^\.(?:[A-Za-z0-9][A-Za-z0-9_.@-]*|[A-Za-z0-9][A-Za-z0-9_-]*\/[A-Za-z0-9_.@/-]+)$/,
|
|
489
|
+
pluginRootNames: /^\.(?!git(?:hub)?$)[A-Za-z0-9][A-Za-z0-9_-]*$/,
|
|
490
|
+
})
|
|
491
|
+
const PROTECTED_PRODUCT_DOCUMENTS = new Set(['CHANGELOG.md', 'CODE_OF_CONDUCT.md', 'CONTRIBUTING.md', 'LICENSE.md', 'README.md', 'SECURITY.md'].map((path) => portableRepositoryPathKey(path)))
|
|
492
|
+
const sha256 = (content) => createHash('sha256').update(content).digest('hex')
|
|
493
|
+
const fileSha256 = (path) => sha256(readFileSync(path))
|
|
494
|
+
const exactKeys = (value, keys, label) => {
|
|
495
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`${label} must be an object`)
|
|
496
|
+
if (JSON.stringify(Object.keys(value).sort()) !== JSON.stringify([...keys].sort())) throw new Error(`${label} has an open or incomplete shape`)
|
|
497
|
+
return value
|
|
498
|
+
}
|
|
499
|
+
// Structural/tamper mirror only. Canonical selection and certification semantics remain owned by
|
|
500
|
+
// packages/governance/src/provider-review-binding.mjs after the shipped corpus is authenticated.
|
|
501
|
+
const stableProjectionValue = (value) => {
|
|
502
|
+
if (Array.isArray(value)) return value.map(stableProjectionValue)
|
|
503
|
+
if (value && typeof value === 'object') {
|
|
504
|
+
return Object.fromEntries(
|
|
505
|
+
Object.keys(value).sort(compareUtf8Bytes).map((key) => [key, stableProjectionValue(value[key])]),
|
|
506
|
+
)
|
|
507
|
+
}
|
|
508
|
+
return value
|
|
509
|
+
}
|
|
510
|
+
const validateReviewInvocation = (value, label) => {
|
|
511
|
+
if (value?.strategy === 'main-context') {
|
|
512
|
+
exactKeys(value, ['strategy'], label)
|
|
513
|
+
return value
|
|
514
|
+
}
|
|
515
|
+
exactKeys(value, ['agent', 'context', 'strategy'], label)
|
|
516
|
+
if (
|
|
517
|
+
value.strategy !== 'native-context-fork'
|
|
518
|
+
|| value.context !== 'fork'
|
|
519
|
+
|| !/^[a-z][a-z0-9-]*$/.test(value.agent || '')
|
|
520
|
+
) throw new Error(`${label} is invalid`)
|
|
521
|
+
return value
|
|
522
|
+
}
|
|
523
|
+
const validateIndependentReviewProjection = (value) => {
|
|
524
|
+
exactKeys(value, [
|
|
525
|
+
'bindings', 'certificationContract', 'certifications', 'kind', 'projectionDigest',
|
|
526
|
+
'providerCompatibility', 'repositoryRole', 'schemaVersion', 'skill',
|
|
527
|
+
], 'installed independent-review projection')
|
|
528
|
+
if (
|
|
529
|
+
value.schemaVersion !== 2
|
|
530
|
+
|| value.kind !== 'provider-neutral-independent-review-projection'
|
|
531
|
+
|| value.skill !== 'independent-review'
|
|
532
|
+
|| value.repositoryRole !== 'product-consumer'
|
|
533
|
+
|| value.certificationContract !== 'exact-provider-runtime-surface-role-target-v1'
|
|
534
|
+
|| !SHA256.test(value.projectionDigest || '')
|
|
535
|
+
|| !Array.isArray(value.bindings)
|
|
536
|
+
|| value.bindings.length === 0
|
|
537
|
+
|| !Array.isArray(value.providerCompatibility)
|
|
538
|
+
|| value.providerCompatibility.length === 0
|
|
539
|
+
|| !Array.isArray(value.certifications)
|
|
540
|
+
) throw new Error('installed independent-review projection is invalid')
|
|
541
|
+
const bindingIds = []
|
|
542
|
+
for (const [index, binding] of value.bindings.entries()) {
|
|
543
|
+
exactKeys(binding, [
|
|
544
|
+
'bindingDigest', 'certificationContract', 'invocation', 'requiredAssuranceTier',
|
|
545
|
+
'requiredComputeTier', 'requiredReasoningTier', 'reviewClass', 'selectionPolicy',
|
|
546
|
+
'selfProviderId', 'transport',
|
|
547
|
+
], `installed independent-review binding[${index}]`)
|
|
548
|
+
if (
|
|
549
|
+
!/^[a-z][a-z0-9-]*$/.test(binding.selfProviderId || '')
|
|
550
|
+
|| !/^[a-z][a-z0-9-]*-v[1-9][0-9]*$/.test(binding.selectionPolicy || '')
|
|
551
|
+
|| binding.reviewClass !== 'tier-0-governance'
|
|
552
|
+
|| binding.requiredAssuranceTier !== 'maximum'
|
|
553
|
+
|| binding.requiredReasoningTier !== 'maximum'
|
|
554
|
+
|| binding.requiredComputeTier !== 'maximum'
|
|
555
|
+
|| binding.transport !== 'content-addressed-model-broker-exchange-v1'
|
|
556
|
+
|| binding.certificationContract !== 'exact-provider-runtime-surface-role-target-v1'
|
|
557
|
+
|| !SHA256.test(binding.bindingDigest || '')
|
|
558
|
+
) throw new Error(`installed independent-review binding[${index}] is invalid`)
|
|
559
|
+
validateReviewInvocation(binding.invocation, `installed independent-review binding[${index}].invocation`)
|
|
560
|
+
bindingIds.push(binding.selfProviderId)
|
|
561
|
+
}
|
|
562
|
+
const compatibilityProviderIds = []
|
|
563
|
+
const compatibilityRuntimeIds = []
|
|
564
|
+
for (const [index, compatibility] of value.providerCompatibility.entries()) {
|
|
565
|
+
exactKeys(compatibility, ['compatibilityProviderId', 'providerId'], `installed independent-review provider compatibility[${index}]`)
|
|
566
|
+
if (
|
|
567
|
+
!/^[a-z][a-z0-9-]*$/.test(compatibility.providerId || '')
|
|
568
|
+
|| !/^[a-z][a-z0-9-]*$/.test(compatibility.compatibilityProviderId || '')
|
|
569
|
+
) throw new Error(`installed independent-review provider compatibility[${index}] is invalid`)
|
|
570
|
+
compatibilityProviderIds.push(compatibility.providerId)
|
|
571
|
+
compatibilityRuntimeIds.push(compatibility.compatibilityProviderId)
|
|
572
|
+
}
|
|
573
|
+
if (
|
|
574
|
+
JSON.stringify(bindingIds) !== JSON.stringify([...new Set(bindingIds)].sort(compareUtf8Bytes))
|
|
575
|
+
|| JSON.stringify(compatibilityProviderIds) !== JSON.stringify([...new Set(compatibilityProviderIds)].sort(compareUtf8Bytes))
|
|
576
|
+
|| new Set(compatibilityRuntimeIds).size !== compatibilityRuntimeIds.length
|
|
577
|
+
|| bindingIds.some((providerId) => !compatibilityProviderIds.includes(providerId))
|
|
578
|
+
) throw new Error('installed independent-review provider compatibility is incomplete or ambiguous')
|
|
579
|
+
const expectedDigest = sha256(JSON.stringify(stableProjectionValue({
|
|
580
|
+
skill: value.skill,
|
|
581
|
+
repositoryRole: value.repositoryRole,
|
|
582
|
+
bindings: value.bindings,
|
|
583
|
+
providerCompatibility: value.providerCompatibility,
|
|
584
|
+
certifications: value.certifications,
|
|
585
|
+
})))
|
|
586
|
+
if (value.projectionDigest !== expectedDigest) throw new Error('installed independent-review projection digest is invalid')
|
|
587
|
+
return value
|
|
588
|
+
}
|
|
589
|
+
const regularUnaliasedFile = (root, path, label) => {
|
|
590
|
+
assertNoSymlinkPath(root, path, label, { allowMissing: false })
|
|
591
|
+
const info = lstatSync(path)
|
|
592
|
+
if (!info.isFile() || info.isSymbolicLink() || info.nlink !== 1) throw new Error(`${label} must be one regular unaliased file`)
|
|
593
|
+
return path
|
|
594
|
+
}
|
|
595
|
+
const validateSurface = (surface, label) => {
|
|
596
|
+
exactKeys(surface, ['kind', 'path'], label)
|
|
597
|
+
if (!['file', 'tree'].includes(surface.kind)) throw new Error(`${label}.kind is invalid`)
|
|
598
|
+
const normalized = normalizeRepositoryRelative(surface.path, `${label}.path`)
|
|
599
|
+
const portable = portableRepositoryPathKey(normalized)
|
|
600
|
+
if (['.git', '.github'].includes(portable.split('/')[0])) throw new Error(`${label}.path addresses a protected Git root`)
|
|
601
|
+
if (PROTECTED_PRODUCT_DOCUMENTS.has(portable)) throw new Error(`${label}.path addresses a protected product document`)
|
|
602
|
+
return { path: normalized, kind: surface.kind }
|
|
603
|
+
}
|
|
604
|
+
const validateRetiredSurface = (surface, label) => {
|
|
605
|
+
exactKeys(surface, ['kind', 'path', 'sha256'], label)
|
|
606
|
+
if (!SHA256.test(surface.sha256 || '')) throw new Error(`${label}.sha256 is invalid`)
|
|
607
|
+
return { ...validateSurface({ path: surface.path, kind: surface.kind }, label), sha256: surface.sha256 }
|
|
608
|
+
}
|
|
609
|
+
export function providerSurfaceDigest(projectDir, surface) {
|
|
610
|
+
projectDir = canonicalRepositoryRoot(projectDir)
|
|
611
|
+
const normalized = validateSurface({ path: surface.path, kind: surface.kind }, 'provider digest surface')
|
|
612
|
+
const target = join(projectDir, normalized.path)
|
|
613
|
+
assertNoSymlinkPath(projectDir, target, `provider digest target ${normalized.path}`, { allowMissing: false })
|
|
614
|
+
const info = lstatSync(target)
|
|
615
|
+
if (normalized.kind === 'file') {
|
|
616
|
+
if (!info.isFile() || info.nlink !== 1) throw new Error(`provider digest file is not regular and unaliased:${normalized.path}`)
|
|
617
|
+
return fileSha256(target)
|
|
618
|
+
}
|
|
619
|
+
if (!info.isDirectory()) throw new Error(`provider digest tree is not a directory:${normalized.path}`)
|
|
620
|
+
const rows = []
|
|
621
|
+
const visit = (absolute) => {
|
|
622
|
+
const entry = lstatSync(absolute)
|
|
623
|
+
const rel = relative(target, absolute).split(sep).join('/') || '.'
|
|
624
|
+
const mode = (entry.mode & 0o777).toString(8).padStart(4, '0')
|
|
625
|
+
if (entry.isDirectory()) {
|
|
626
|
+
rows.push(`dir:${rel}:${mode}`)
|
|
627
|
+
for (const name of readdirSync(absolute).sort()) visit(join(absolute, name))
|
|
628
|
+
} else if (entry.isFile() && entry.nlink === 1) rows.push(`file:${rel}:${mode}:${fileSha256(absolute)}`)
|
|
629
|
+
else throw new Error(`provider digest tree contains unsafe entry:${normalized.path}/${rel}`)
|
|
630
|
+
}
|
|
631
|
+
visit(target)
|
|
632
|
+
return sha256(rows.join('\n') + '\n')
|
|
633
|
+
}
|
|
634
|
+
const sortedUniqueStrings = (values, label) => {
|
|
635
|
+
if (!Array.isArray(values) || values.some((value) => typeof value !== 'string')) throw new Error(`${label} must be a string array`)
|
|
636
|
+
if (JSON.stringify(values) !== JSON.stringify([...new Set(values)].sort())) throw new Error(`${label} must be sorted and unique`)
|
|
637
|
+
return values
|
|
638
|
+
}
|
|
639
|
+
const validateDiscovery = (discovery, label) => {
|
|
640
|
+
exactKeys(discovery, DISCOVERY_KEYS, label)
|
|
641
|
+
for (const key of DISCOVERY_KEYS) validateDiscoveryEntries(discovery[key], key, `${label}.${key}`)
|
|
642
|
+
return discovery
|
|
643
|
+
}
|
|
644
|
+
const validateDiscoveryEntries = (values, key, label) => {
|
|
645
|
+
const entries = sortedUniqueStrings(values, label)
|
|
646
|
+
const portable = new Set()
|
|
647
|
+
for (const [index, entry] of entries.entries()) {
|
|
648
|
+
const normalized = normalizeRepositoryRelative(entry, `${label}[${index}]`)
|
|
649
|
+
if (normalized !== entry || !DISCOVERY_PATTERNS[key].test(entry)) {
|
|
650
|
+
throw new Error(`${label}[${index}] is not a canonical ${key} discovery path`)
|
|
651
|
+
}
|
|
652
|
+
const alias = portableRepositoryPathKey(normalized)
|
|
653
|
+
if (portable.has(alias)) throw new Error(`${label} contains a portable alias:${entry}`)
|
|
654
|
+
portable.add(alias)
|
|
655
|
+
}
|
|
656
|
+
return entries
|
|
657
|
+
}
|
|
658
|
+
const validateDiscoveryPolicy = (policy, label) => {
|
|
659
|
+
exactKeys(policy, ['schemaVersion', ...DISCOVERY_KEYS], label)
|
|
660
|
+
if (policy.schemaVersion !== 1) throw new Error(`${label}.schemaVersion is unsupported`)
|
|
661
|
+
for (const key of DISCOVERY_KEYS) validateDiscoveryEntries(policy[key], key, `${label}.${key}`)
|
|
662
|
+
return policy
|
|
663
|
+
}
|
|
664
|
+
const assertSnapshotDiscoveryWithinPolicy = (snapshot, policy, label) => {
|
|
665
|
+
const records = [
|
|
666
|
+
...snapshot.providers.map((provider) => ({ ...provider, lifecycleRole: 'provider' })),
|
|
667
|
+
...snapshot.retiredProviders.map((retirement) => ({ ...retirement, lifecycleRole: 'retirement' })),
|
|
668
|
+
]
|
|
669
|
+
for (const record of records) for (const key of DISCOVERY_KEYS) {
|
|
670
|
+
const allowed = new Set(policy[key].map(portableRepositoryPathKey))
|
|
671
|
+
for (const entry of record.discovery[key]) if (!allowed.has(portableRepositoryPathKey(entry))) {
|
|
672
|
+
throw new Error(`${label}.${record.lifecycleRole}.${record.id}.discovery.${key} is outside the signed global discovery policy:${entry}`)
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
const snapshotActiveSurfaceView = (snapshot) => snapshot.providers.map((provider) => ({
|
|
677
|
+
id: provider.id,
|
|
678
|
+
surfaces: provider.surfaces.map(({ path, kind }) => ({ path, kind })),
|
|
679
|
+
}))
|
|
680
|
+
const discoveryPolicyReservesPath = (policy, path) => {
|
|
681
|
+
const pathKey = portableRepositoryPathKey(normalizeRepositoryRelative(path, 'discovery-reserved path'))
|
|
682
|
+
const treeRoots = [...policy.providerRootNames, ...policy.pluginRootNames].map(portableRepositoryPathKey)
|
|
683
|
+
if (treeRoots.some((root) => pathKey === root || pathKey.startsWith(`${root}/`))) return true
|
|
684
|
+
return [
|
|
685
|
+
...policy.instructionNames,
|
|
686
|
+
...policy.instructionOverrideNames,
|
|
687
|
+
...policy.configPaths,
|
|
688
|
+
].some((candidate) => portableRepositoryPathKey(candidate) === pathKey)
|
|
689
|
+
}
|
|
690
|
+
const validateProviderMaterializers = (catalog, label = 'provider materializers') => {
|
|
691
|
+
const groups = ['instructionViews', 'hookBaseTemplateContracts', 'hookViews', 'skillViews', 'treeViews']
|
|
692
|
+
exactKeys(catalog, groups, label)
|
|
693
|
+
for (const group of groups) {
|
|
694
|
+
if (!catalog[group] || typeof catalog[group] !== 'object' || Array.isArray(catalog[group]) || !Object.keys(catalog[group]).length) {
|
|
695
|
+
throw new Error(`${label}.${group} must be a non-empty object`)
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
const validId = (id, group) => {
|
|
699
|
+
if (!/^[a-z][a-z0-9-]*-v[1-9][0-9]*$/.test(id)) throw new Error(`${label}.${group} has invalid id:${id}`)
|
|
700
|
+
}
|
|
701
|
+
for (const [id, materializer] of Object.entries(catalog.instructionViews)) {
|
|
702
|
+
validId(id, 'instructionViews')
|
|
703
|
+
exactKeys(materializer, ['engine', 'ownershipPolicy', 'supplementPolicy'], `${label}.instructionViews.${id}`)
|
|
704
|
+
if (!['shared-instruction-v1', 'markdown-relative-at-import-v1'].includes(materializer.engine)) throw new Error(`${label}.instructionViews.${id} has unsupported engine`)
|
|
705
|
+
if (!['forbidden', 'optional', 'required'].includes(materializer.supplementPolicy)) throw new Error(`${label}.instructionViews.${id}.supplementPolicy is invalid`)
|
|
706
|
+
if (materializer.engine === 'shared-instruction-v1' && (materializer.supplementPolicy !== 'forbidden' || materializer.ownershipPolicy !== 'common-authority-consumer')) throw new Error(`${label}.instructionViews.${id} shared instruction contract is invalid`)
|
|
707
|
+
if (materializer.engine === 'markdown-relative-at-import-v1' && materializer.ownershipPolicy !== 'provider-generated-projection') throw new Error(`${label}.instructionViews.${id} projection ownership is invalid`)
|
|
708
|
+
}
|
|
709
|
+
const commonInstructionMaterializers = Object.entries(catalog.instructionViews).filter(([, materializer]) => (
|
|
710
|
+
materializer.engine === 'shared-instruction-v1'
|
|
711
|
+
&& materializer.ownershipPolicy === 'common-authority-consumer'
|
|
712
|
+
))
|
|
713
|
+
if (commonInstructionMaterializers.length !== 1) {
|
|
714
|
+
throw new Error(`${label}.instructionViews must declare exactly one common-authority shared instruction materializer`)
|
|
715
|
+
}
|
|
716
|
+
for (const [id, contract] of Object.entries(catalog.hookBaseTemplateContracts)) {
|
|
717
|
+
validId(id, 'hookBaseTemplateContracts')
|
|
718
|
+
if (contract?.engine === 'claude-permission-policy-v1') {
|
|
719
|
+
exactKeys(contract, ['engine', 'schema', 'source'], `${label}.hookBaseTemplateContracts.${id}`)
|
|
720
|
+
if (contract.source !== CLAUDE_PERMISSION_POLICY_SOURCE || typeof contract.schema !== 'string' || !contract.schema) throw new Error(`${label}.hookBaseTemplateContracts.${id} is invalid`)
|
|
721
|
+
} else if (contract?.engine === 'static-json-object-v1') exactKeys(contract, ['engine'], `${label}.hookBaseTemplateContracts.${id}`)
|
|
722
|
+
else throw new Error(`${label}.hookBaseTemplateContracts.${id} has unsupported engine`)
|
|
723
|
+
}
|
|
724
|
+
for (const [id, materializer] of Object.entries(catalog.hookViews)) {
|
|
725
|
+
validId(id, 'hookViews')
|
|
726
|
+
if (materializer?.engine === 'json-hook-map-v1') {
|
|
727
|
+
exactKeys(materializer, ['baseTemplateContract', 'descriptionField', 'engine', 'hooksField', 'mergeStrategy'], `${label}.hookViews.${id}`)
|
|
728
|
+
if (typeof materializer.hooksField !== 'string' || !materializer.hooksField) throw new Error(`${label}.hookViews.${id}.hooksField is invalid`)
|
|
729
|
+
} else if (materializer?.engine === 'json-hook-event-list-v1') {
|
|
730
|
+
exactKeys(materializer, ['baseTemplateContract', 'descriptionField', 'engine', 'eventField', 'eventsField', 'groupsField', 'handlerArgvField', 'handlersField', 'matcherField', 'mergeStrategy'], `${label}.hookViews.${id}`)
|
|
731
|
+
for (const field of ['eventField', 'eventsField', 'groupsField', 'handlerArgvField', 'handlersField', 'matcherField']) {
|
|
732
|
+
if (typeof materializer[field] !== 'string' || !materializer[field]) throw new Error(`${label}.hookViews.${id}.${field} is invalid`)
|
|
733
|
+
}
|
|
734
|
+
} else throw new Error(`${label}.hookViews.${id} has unsupported engine:${materializer?.engine || '<missing>'}`)
|
|
735
|
+
if (
|
|
736
|
+
(materializer.engine === 'json-hook-event-list-v1' && (typeof materializer.descriptionField !== 'string' || !materializer.descriptionField))
|
|
737
|
+
|| (materializer.engine === 'json-hook-map-v1' && materializer.descriptionField !== null && (typeof materializer.descriptionField !== 'string' || !materializer.descriptionField))
|
|
738
|
+
) {
|
|
739
|
+
throw new Error(`${label}.hookViews.${id}.descriptionField is invalid`)
|
|
740
|
+
}
|
|
741
|
+
if (!['merge-hook-map-into-base-v1', 'replace-document-v1'].includes(materializer.mergeStrategy)) throw new Error(`${label}.hookViews.${id}.mergeStrategy is invalid`)
|
|
742
|
+
if (materializer.mergeStrategy === 'merge-hook-map-into-base-v1') {
|
|
743
|
+
if (typeof materializer.baseTemplateContract !== 'string' || !catalog.hookBaseTemplateContracts[materializer.baseTemplateContract]) throw new Error(`${label}.hookViews.${id}.baseTemplateContract is invalid`)
|
|
744
|
+
} else if (materializer.baseTemplateContract !== null) throw new Error(`${label}.hookViews.${id} replace-document must not bind a base template`)
|
|
745
|
+
}
|
|
746
|
+
for (const [id, materializer] of Object.entries(catalog.skillViews)) {
|
|
747
|
+
validId(id, 'skillViews')
|
|
748
|
+
exactKeys(materializer, ['engine', 'entryFile'], `${label}.skillViews.${id}`)
|
|
749
|
+
if (materializer.engine !== 'skill-directory-v1') throw new Error(`${label}.skillViews.${id} has unsupported engine:${materializer.engine}`)
|
|
750
|
+
if (typeof materializer.entryFile !== 'string' || !/^[A-Za-z0-9._-]+\.md$/.test(materializer.entryFile)) {
|
|
751
|
+
throw new Error(`${label}.skillViews.${id}.entryFile is invalid`)
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
for (const [id, materializer] of Object.entries(catalog.treeViews)) {
|
|
755
|
+
validId(id, 'treeViews')
|
|
756
|
+
exactKeys(materializer, ['engine', 'transformPolicies'], `${label}.treeViews.${id}`)
|
|
757
|
+
if (materializer.engine !== 'closed-tree-copy-v1' || JSON.stringify(materializer.transformPolicies) !== JSON.stringify(['byte-exact'])) throw new Error(`${label}.treeViews.${id} is unsupported`)
|
|
758
|
+
}
|
|
759
|
+
return catalog
|
|
760
|
+
}
|
|
761
|
+
const resolveManifestMaterializer = (manifest, group, id, label) => {
|
|
762
|
+
const catalog = validateProviderMaterializers(manifest?.materializers, 'installed provider materializers')
|
|
763
|
+
if (typeof id !== 'string' || !Object.hasOwn(catalog[group] || {}, id)) {
|
|
764
|
+
throw new Error(`${label} references unknown ${group} materializer:${id || '<missing>'}`)
|
|
765
|
+
}
|
|
766
|
+
return catalog[group][id]
|
|
767
|
+
}
|
|
768
|
+
const compareVersion = (leftValue, rightValue) => {
|
|
769
|
+
const parse = (value) => {
|
|
770
|
+
const match = String(value || '').match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/)
|
|
771
|
+
return match ? { core: match.slice(1, 4).map(Number), pre: match[4]?.split('.') ?? null } : null
|
|
772
|
+
}
|
|
773
|
+
const left = parse(leftValue)
|
|
774
|
+
const right = parse(rightValue)
|
|
775
|
+
if (!left || !right) throw new Error(`provider transition releases must be exact semver:${leftValue}:${rightValue}`)
|
|
776
|
+
for (let index = 0; index < 3; index += 1) if (left.core[index] !== right.core[index]) return left.core[index] - right.core[index]
|
|
777
|
+
if (left.pre === null || right.pre === null) return left.pre === right.pre ? 0 : left.pre === null ? 1 : -1
|
|
778
|
+
for (let index = 0; index < Math.max(left.pre.length, right.pre.length); index += 1) {
|
|
779
|
+
if (left.pre[index] === undefined || right.pre[index] === undefined) return left.pre[index] === undefined ? -1 : 1
|
|
780
|
+
if (left.pre[index] === right.pre[index]) continue
|
|
781
|
+
const leftNumeric = /^\d+$/.test(left.pre[index])
|
|
782
|
+
const rightNumeric = /^\d+$/.test(right.pre[index])
|
|
783
|
+
if (leftNumeric && rightNumeric) return Number(left.pre[index]) - Number(right.pre[index])
|
|
784
|
+
if (leftNumeric !== rightNumeric) return leftNumeric ? -1 : 1
|
|
785
|
+
return compareUtf8Bytes(left.pre[index], right.pre[index])
|
|
786
|
+
}
|
|
787
|
+
return 0
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
export function validateForkManifestProviderSurfaces(manifest, label = 'installed manifest') {
|
|
791
|
+
validateProviderMaterializers(manifest?.materializers, `${label}.materializers`)
|
|
792
|
+
const commonInstruction = exactKeys(manifest?.consumer?.commonInstruction, [
|
|
793
|
+
'artifact', 'destination', 'materializerId', 'schemaVersion',
|
|
794
|
+
], `${label}.consumer.commonInstruction`)
|
|
795
|
+
const commonMaterializer = resolveManifestMaterializer(
|
|
796
|
+
manifest,
|
|
797
|
+
'instructionViews',
|
|
798
|
+
commonInstruction.materializerId,
|
|
799
|
+
`${label}.consumer.commonInstruction.materializerId`,
|
|
800
|
+
)
|
|
801
|
+
const commonDestination = normalizeRepositoryRelative(
|
|
802
|
+
commonInstruction.destination,
|
|
803
|
+
`${label}.consumer.commonInstruction.destination`,
|
|
804
|
+
)
|
|
805
|
+
if (
|
|
806
|
+
commonInstruction.schemaVersion !== 1
|
|
807
|
+
|| commonInstruction.artifact !== 'common/instruction.md'
|
|
808
|
+
|| commonInstruction.destination !== commonDestination
|
|
809
|
+
|| commonMaterializer.engine !== 'shared-instruction-v1'
|
|
810
|
+
|| commonMaterializer.ownershipPolicy !== 'common-authority-consumer'
|
|
811
|
+
) throw new Error(`${label}.consumer.commonInstruction is not the closed common authority record`)
|
|
812
|
+
const providers = []
|
|
813
|
+
for (const [providerId, surface] of Object.entries(manifest?.providerSurfaces || {}).sort(([left], [right]) => compareUtf8Bytes(left, right))) {
|
|
814
|
+
const surfaceLabel = `${label}.providerSurfaces.${providerId}`
|
|
815
|
+
exactKeys(surface, [
|
|
816
|
+
'capabilities', 'environmentMap', 'exclusions', 'generated', 'hookArtifact', 'hookDestination',
|
|
817
|
+
'hookMaterializerId', 'instructionArtifact', 'instructionDestination', 'instructionMaterializerId', 'managedSurfaces', 'schemaVersion',
|
|
818
|
+
'skillArtifactRoot', 'skillDestination', 'skillEntryFile', 'skillMaterializerId', 'skills', 'trees', 'nativeHookCoverage',
|
|
819
|
+
], surfaceLabel)
|
|
820
|
+
if (surface.schemaVersion !== 3 || typeof surface.generated !== 'boolean' || !Array.isArray(surface.skills) || !Array.isArray(surface.trees) || !Array.isArray(surface.managedSurfaces)) {
|
|
821
|
+
throw new Error(`${surfaceLabel} identity is invalid`)
|
|
822
|
+
}
|
|
823
|
+
const managedSurfaces = surface.managedSurfaces.map((record, index) => validateSurface(record, `${surfaceLabel}.managedSurfaces[${index}]`))
|
|
824
|
+
if (JSON.stringify(managedSurfaces) !== JSON.stringify([...managedSurfaces].sort((left, right) => compareUtf8Bytes(left.path, right.path)))) {
|
|
825
|
+
throw new Error(`${surfaceLabel}.managedSurfaces must be sorted by path`)
|
|
826
|
+
}
|
|
827
|
+
if (new Set(managedSurfaces.map((record) => portableRepositoryPathKey(record.path))).size !== managedSurfaces.length) {
|
|
828
|
+
throw new Error(`${surfaceLabel}.managedSurfaces must be unique by path`)
|
|
829
|
+
}
|
|
830
|
+
for (let left = 0; left < managedSurfaces.length; left += 1) for (let right = left + 1; right < managedSurfaces.length; right += 1) {
|
|
831
|
+
if (repositoryPathsOverlap(managedSurfaces[left].path, managedSurfaces[right].path)) {
|
|
832
|
+
throw new Error(`${surfaceLabel}.managedSurfaces contain overlapping portable paths:${managedSurfaces[left].path} <> ${managedSurfaces[right].path}`)
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
if (!surface.generated) {
|
|
836
|
+
if (managedSurfaces.length) throw new Error(`${surfaceLabel} disabled provider may not claim managed surfaces`)
|
|
837
|
+
if (surface.trees.length || surface.nativeHookCoverage !== null) throw new Error(`${surfaceLabel} disabled provider may not claim materialized trees or native coverage`)
|
|
838
|
+
if (surface.instructionMaterializerId !== null || surface.hookMaterializerId !== null || surface.skillMaterializerId !== null || surface.skillEntryFile !== null) {
|
|
839
|
+
throw new Error(`${surfaceLabel} disabled provider may not bind materializers`)
|
|
840
|
+
}
|
|
841
|
+
continue
|
|
842
|
+
}
|
|
843
|
+
if (!managedSurfaces.length) throw new Error(`${surfaceLabel} generated provider needs managed surfaces`)
|
|
844
|
+
const instructionMaterializer = resolveManifestMaterializer(manifest, 'instructionViews', surface.instructionMaterializerId, `${surfaceLabel}.instructionMaterializerId`)
|
|
845
|
+
if (surface.hookMaterializerId !== null) {
|
|
846
|
+
resolveManifestMaterializer(manifest, 'hookViews', surface.hookMaterializerId, `${surfaceLabel}.hookMaterializerId`)
|
|
847
|
+
}
|
|
848
|
+
const skillMaterializer = resolveManifestMaterializer(manifest, 'skillViews', surface.skillMaterializerId, `${surfaceLabel}.skillMaterializerId`)
|
|
849
|
+
if (surface.skillEntryFile !== skillMaterializer.entryFile) throw new Error(`${surfaceLabel}.skillEntryFile differs from its registered materializer`)
|
|
850
|
+
const coverage = exactKeys(surface.nativeHookCoverage, [
|
|
851
|
+
'canonicalEvents', 'excludedEvents', 'projectedEvents', 'projectedNativeEvents', 'schemaVersion',
|
|
852
|
+
], `${surfaceLabel}.nativeHookCoverage`)
|
|
853
|
+
const canonicalEvents = sortedUniqueStrings(coverage.canonicalEvents, `${surfaceLabel}.nativeHookCoverage.canonicalEvents`)
|
|
854
|
+
const projectedEvents = sortedUniqueStrings(coverage.projectedEvents, `${surfaceLabel}.nativeHookCoverage.projectedEvents`)
|
|
855
|
+
if (coverage.schemaVersion !== 2 || !canonicalEvents.length) throw new Error(`${surfaceLabel}.nativeHookCoverage identity is invalid`)
|
|
856
|
+
if (!Array.isArray(coverage.projectedNativeEvents)) throw new Error(`${surfaceLabel}.nativeHookCoverage.projectedNativeEvents must be an array`)
|
|
857
|
+
const projectedNativeEvents = coverage.projectedNativeEvents.map((record, index) => {
|
|
858
|
+
exactKeys(record, ['canonicalEvent', 'nativeEvent'], `${surfaceLabel}.nativeHookCoverage.projectedNativeEvents[${index}]`)
|
|
859
|
+
if (
|
|
860
|
+
typeof record.canonicalEvent !== 'string'
|
|
861
|
+
|| !record.canonicalEvent
|
|
862
|
+
|| typeof record.nativeEvent !== 'string'
|
|
863
|
+
|| !/^[A-Za-z][A-Za-z0-9_.:@/-]*$/.test(record.nativeEvent)
|
|
864
|
+
) throw new Error(`${surfaceLabel}.nativeHookCoverage.projectedNativeEvents[${index}] is invalid`)
|
|
865
|
+
return record
|
|
866
|
+
})
|
|
867
|
+
if (JSON.stringify(projectedNativeEvents) !== JSON.stringify([...projectedNativeEvents].sort((left, right) => compareUtf8Bytes(left.canonicalEvent, right.canonicalEvent)))) {
|
|
868
|
+
throw new Error(`${surfaceLabel}.nativeHookCoverage.projectedNativeEvents must be sorted by canonicalEvent`)
|
|
869
|
+
}
|
|
870
|
+
if (!Array.isArray(coverage.excludedEvents)) throw new Error(`${surfaceLabel}.nativeHookCoverage.excludedEvents must be an array`)
|
|
871
|
+
const excludedEvents = coverage.excludedEvents.map((record, index) => {
|
|
872
|
+
exactKeys(record, ['authoritativeFallback', 'event', 'reasonCode'], `${surfaceLabel}.nativeHookCoverage.excludedEvents[${index}]`)
|
|
873
|
+
if (
|
|
874
|
+
typeof record.event !== 'string'
|
|
875
|
+
|| !record.event
|
|
876
|
+
|| !/^[A-Z][A-Z0-9_]*$/.test(record.reasonCode || '')
|
|
877
|
+
|| record.authoritativeFallback !== 'immutable-governance-check-and-protected-ci'
|
|
878
|
+
) throw new Error(`${surfaceLabel}.nativeHookCoverage.excludedEvents[${index}] is invalid`)
|
|
879
|
+
return record
|
|
880
|
+
})
|
|
881
|
+
if (JSON.stringify(excludedEvents) !== JSON.stringify([...excludedEvents].sort((left, right) => compareUtf8Bytes(left.event, right.event)))) {
|
|
882
|
+
throw new Error(`${surfaceLabel}.nativeHookCoverage.excludedEvents must be sorted by event`)
|
|
883
|
+
}
|
|
884
|
+
const excludedNames = excludedEvents.map((record) => record.event)
|
|
885
|
+
if (
|
|
886
|
+
excludedNames.length !== new Set(excludedNames).size
|
|
887
|
+
|| projectedEvents.some((event) => excludedNames.includes(event))
|
|
888
|
+
|| JSON.stringify([...projectedEvents, ...excludedNames].sort()) !== JSON.stringify(canonicalEvents)
|
|
889
|
+
|| JSON.stringify(projectedNativeEvents.map((record) => record.canonicalEvent)) !== JSON.stringify(projectedEvents)
|
|
890
|
+
|| new Set(projectedNativeEvents.map((record) => record.nativeEvent)).size !== projectedNativeEvents.length
|
|
891
|
+
|| (!surface.capabilities?.nativeHooks && projectedEvents.length)
|
|
892
|
+
|| (!surface.capabilities?.nativeHooks && excludedEvents.some((record) => record.reasonCode !== 'NATIVE_HOOK_API_UNAVAILABLE'))
|
|
893
|
+
|| (surface.capabilities?.nativeHooks && excludedEvents.some((record) => record.reasonCode !== 'EVENT_NOT_SUPPORTED'))
|
|
894
|
+
) throw new Error(`${surfaceLabel}.nativeHookCoverage is not an exact projected/excluded partition`)
|
|
895
|
+
if (surface.capabilities?.nativeHooks) {
|
|
896
|
+
if (!surface.hookArtifact || !surface.hookDestination || !surface.hookMaterializerId) throw new Error(`${surfaceLabel} native hooks lack a materialized hook surface`)
|
|
897
|
+
} else if (surface.hookArtifact !== null || surface.hookDestination !== null || surface.hookMaterializerId !== null) {
|
|
898
|
+
throw new Error(`${surfaceLabel} hookless provider claims a hook surface`)
|
|
899
|
+
}
|
|
900
|
+
if (instructionMaterializer.engine === 'shared-instruction-v1') {
|
|
901
|
+
if (
|
|
902
|
+
surface.instructionArtifact !== commonInstruction.artifact
|
|
903
|
+
|| surface.instructionDestination !== commonInstruction.destination
|
|
904
|
+
|| surface.instructionMaterializerId !== commonInstruction.materializerId
|
|
905
|
+
|| managedSurfaces.some((record) => pathsOverlap(record.path, commonInstruction.destination))
|
|
906
|
+
) throw new Error(`${surfaceLabel} shared instruction does not reference the common non-provider authority`)
|
|
907
|
+
} else if (!managedSurfaces.some((record) => (
|
|
908
|
+
record.path === normalizeRepositoryRelative(surface.instructionDestination, `${surfaceLabel}.instructionDestination`)
|
|
909
|
+
&& record.kind === 'file'
|
|
910
|
+
))) throw new Error(`${surfaceLabel}.instructionDestination is absent from managedSurfaces`)
|
|
911
|
+
for (const [path, kind, field] of [
|
|
912
|
+
[surface.hookDestination, 'file', 'hookDestination'],
|
|
913
|
+
[surface.skillDestination, 'tree', 'skillDestination'],
|
|
914
|
+
]) {
|
|
915
|
+
if (!path) continue
|
|
916
|
+
const normalized = normalizeRepositoryRelative(path, `${surfaceLabel}.${field}`)
|
|
917
|
+
if (!managedSurfaces.some((record) => record.path === normalized && record.kind === kind)) {
|
|
918
|
+
throw new Error(`${surfaceLabel}.${field} is absent from managedSurfaces`)
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
const trees = surface.trees.map((tree, index) => {
|
|
922
|
+
const treeLabel = `${surfaceLabel}.trees[${index}]`
|
|
923
|
+
exactKeys(tree, ['artifactRoot', 'destination', 'materializerId', 'name', 'schemaVersion', 'transformPolicy'], treeLabel)
|
|
924
|
+
if (tree.schemaVersion !== 1 || !/^[a-z][a-z0-9-]*$/.test(tree.name || '')) throw new Error(`${treeLabel} identity is invalid`)
|
|
925
|
+
const artifactRoot = normalizeRepositoryRelative(tree.artifactRoot, `${treeLabel}.artifactRoot`)
|
|
926
|
+
const destination = normalizeRepositoryRelative(tree.destination, `${treeLabel}.destination`)
|
|
927
|
+
if (artifactRoot !== `providers/${providerId}/trees/${tree.name}`) throw new Error(`${treeLabel}.artifactRoot is not provider/name scoped`)
|
|
928
|
+
const materializer = resolveManifestMaterializer(manifest, 'treeViews', tree.materializerId, `${treeLabel}.materializerId`)
|
|
929
|
+
if (
|
|
930
|
+
materializer.engine !== 'closed-tree-copy-v1'
|
|
931
|
+
|| tree.transformPolicy !== 'byte-exact'
|
|
932
|
+
|| !materializer.transformPolicies.includes(tree.transformPolicy)
|
|
933
|
+
) throw new Error(`${treeLabel} has an unsupported tree materializer contract`)
|
|
934
|
+
if (!managedSurfaces.some((record) => record.path === destination && record.kind === 'tree')) {
|
|
935
|
+
throw new Error(`${treeLabel}.destination is absent from managedSurfaces`)
|
|
936
|
+
}
|
|
937
|
+
return { ...tree, artifactRoot, destination }
|
|
938
|
+
})
|
|
939
|
+
if (
|
|
940
|
+
JSON.stringify(trees) !== JSON.stringify([...trees].sort((left, right) => compareUtf8Bytes(left.name, right.name)))
|
|
941
|
+
|| new Set(trees.map((tree) => tree.name)).size !== trees.length
|
|
942
|
+
|| new Set(trees.map((tree) => portableRepositoryPathKey(tree.destination))).size !== trees.length
|
|
943
|
+
) throw new Error(`${surfaceLabel}.trees must be sorted and unique by name/destination`)
|
|
944
|
+
const expectedManagedTrees = [
|
|
945
|
+
normalizeRepositoryRelative(surface.skillDestination, `${surfaceLabel}.skillDestination`),
|
|
946
|
+
...trees.map((tree) => tree.destination),
|
|
947
|
+
].sort()
|
|
948
|
+
const actualManagedTrees = managedSurfaces.filter((record) => record.kind === 'tree').map((record) => record.path).sort()
|
|
949
|
+
if (JSON.stringify(actualManagedTrees) !== JSON.stringify(expectedManagedTrees)) {
|
|
950
|
+
throw new Error(`${surfaceLabel}.managedSurfaces trees must exactly match skillDestination plus materialized trees`)
|
|
951
|
+
}
|
|
952
|
+
const compatibilityFiles = (manifest?.consumer?.compatibilityCategories || [])
|
|
953
|
+
.filter((category) => category?.providerId === providerId)
|
|
954
|
+
.flatMap((category, categoryIndex) => {
|
|
955
|
+
const destinationRoot = normalizeRepositoryRelative(category.destinationRoot, `${surfaceLabel}.compatibilityCategories[${categoryIndex}].destinationRoot`)
|
|
956
|
+
if (!Array.isArray(category.entries)) throw new Error(`${surfaceLabel}.compatibilityCategories[${categoryIndex}].entries must be an array`)
|
|
957
|
+
return category.entries.map((entry, entryIndex) => normalizeRepositoryRelative(
|
|
958
|
+
`${destinationRoot}/${entry}`,
|
|
959
|
+
`${surfaceLabel}.compatibilityCategories[${categoryIndex}].entries[${entryIndex}]`,
|
|
960
|
+
))
|
|
961
|
+
})
|
|
962
|
+
const expectedManagedFiles = [
|
|
963
|
+
...(instructionMaterializer.engine === 'shared-instruction-v1'
|
|
964
|
+
? []
|
|
965
|
+
: [normalizeRepositoryRelative(surface.instructionDestination, `${surfaceLabel}.instructionDestination`)]),
|
|
966
|
+
...(surface.hookDestination
|
|
967
|
+
? [normalizeRepositoryRelative(surface.hookDestination, `${surfaceLabel}.hookDestination`)]
|
|
968
|
+
: []),
|
|
969
|
+
...compatibilityFiles,
|
|
970
|
+
].sort()
|
|
971
|
+
const actualManagedFiles = managedSurfaces.filter((record) => record.kind === 'file').map((record) => record.path).sort()
|
|
972
|
+
if (JSON.stringify(actualManagedFiles) !== JSON.stringify(expectedManagedFiles)) {
|
|
973
|
+
throw new Error(`${surfaceLabel}.managedSurfaces files must exactly match non-shared instruction, hook, and compatibility leaves`)
|
|
974
|
+
}
|
|
975
|
+
providers.push({ id: providerId, surfaces: managedSurfaces })
|
|
976
|
+
}
|
|
977
|
+
const claims = providers.flatMap((provider) => provider.surfaces.map((record) => ({
|
|
978
|
+
...record,
|
|
979
|
+
providerId: provider.id,
|
|
980
|
+
}))).sort((left, right) => (
|
|
981
|
+
compareUtf8Bytes(left.path, right.path) || compareUtf8Bytes(left.providerId, right.providerId)
|
|
982
|
+
))
|
|
983
|
+
for (let left = 0; left < claims.length; left += 1) for (let right = left + 1; right < claims.length; right += 1) {
|
|
984
|
+
if (
|
|
985
|
+
claims[left].providerId !== claims[right].providerId
|
|
986
|
+
&& repositoryPathsOverlap(claims[left].path, claims[right].path)
|
|
987
|
+
) {
|
|
988
|
+
throw new Error(
|
|
989
|
+
`${label} has cross-provider managed surface overlap:`
|
|
990
|
+
+ `${claims[left].providerId}:${claims[left].path} <> ${claims[right].providerId}:${claims[right].path}`,
|
|
991
|
+
)
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
const nonProviderClaims = [
|
|
995
|
+
{ path: commonDestination, kind: 'file', owner: 'common-instruction' },
|
|
996
|
+
{
|
|
997
|
+
path: normalizeRepositoryRelative(manifest?.consumer?.launcherDestination, `${label}.consumer.launcherDestination`),
|
|
998
|
+
kind: 'tree',
|
|
999
|
+
owner: 'shared-launchers',
|
|
1000
|
+
},
|
|
1001
|
+
...Object.keys(manifest?.consumer?.managedFiles || {}).map((path) => ({
|
|
1002
|
+
path: normalizeRepositoryRelative(path, `${label}.consumer.managedFiles destination`),
|
|
1003
|
+
kind: 'file',
|
|
1004
|
+
owner: 'consumer-managed-file',
|
|
1005
|
+
})),
|
|
1006
|
+
...CONSUMER_CONTROL_PLANE_PATHS.map((path) => ({
|
|
1007
|
+
path,
|
|
1008
|
+
kind: 'file',
|
|
1009
|
+
owner: 'consumer-control-plane',
|
|
1010
|
+
})),
|
|
1011
|
+
]
|
|
1012
|
+
for (const claim of nonProviderClaims.filter((item) => item.owner !== 'consumer-control-plane')) {
|
|
1013
|
+
const controlPlaneAuthority = consumerControlPlaneAuthority(claim.path)
|
|
1014
|
+
if (controlPlaneAuthority) {
|
|
1015
|
+
throw new Error(
|
|
1016
|
+
`${label} non-provider materialization authority overlaps consumer control plane:`
|
|
1017
|
+
+ `${claim.owner}:${claim.path} <> ${controlPlaneAuthority}`,
|
|
1018
|
+
)
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
for (let left = 0; left < nonProviderClaims.length; left += 1) for (let right = left + 1; right < nonProviderClaims.length; right += 1) {
|
|
1022
|
+
if (repositoryPathsOverlap(nonProviderClaims[left].path, nonProviderClaims[right].path)) {
|
|
1023
|
+
throw new Error(
|
|
1024
|
+
`${label} non-provider materialization authorities overlap:`
|
|
1025
|
+
+ `${nonProviderClaims[left].owner}:${nonProviderClaims[left].path}`
|
|
1026
|
+
+ ` <> ${nonProviderClaims[right].owner}:${nonProviderClaims[right].path}`,
|
|
1027
|
+
)
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
for (const claim of claims) {
|
|
1031
|
+
const controlPlaneAuthority = consumerControlPlaneAuthority(claim.path)
|
|
1032
|
+
if (controlPlaneAuthority) {
|
|
1033
|
+
throw new Error(
|
|
1034
|
+
`${label} provider surface overlaps non-provider authority:`
|
|
1035
|
+
+ `${claim.providerId}:${claim.path} <> consumer-control-plane:${controlPlaneAuthority}`,
|
|
1036
|
+
)
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
for (const claim of claims) for (const reserved of nonProviderClaims) {
|
|
1040
|
+
if (
|
|
1041
|
+
repositoryPathsOverlap(claim.path, reserved.path)
|
|
1042
|
+
) {
|
|
1043
|
+
throw new Error(
|
|
1044
|
+
`${label} provider surface overlaps non-provider authority:`
|
|
1045
|
+
+ `${claim.providerId}:${claim.path} <> ${reserved.owner}:${reserved.path}`,
|
|
1046
|
+
)
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
return providers
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// The installed consumer BOM is the package trust root, the corpus lock binds every executable
|
|
1053
|
+
// body, and the lock binds the manifest. Refresh must prove that complete chain before its first
|
|
1054
|
+
// write or tombstone removal; a merely well-shaped node_modules/manifest.json is never authority.
|
|
1055
|
+
export function validateInstalledForkCorpus(projectDir) {
|
|
1056
|
+
projectDir = canonicalRepositoryRoot(projectDir)
|
|
1057
|
+
assertNoRootNpmShrinkwrap(projectDir, { errorPrefix: 'GOV-CORPUS-LOCK-001' })
|
|
1058
|
+
const dsRoot = join(projectDir, 'node_modules/@qijenchen/design-system')
|
|
1059
|
+
const forkRoot = join(dsRoot, 'ds-canonical/fork')
|
|
1060
|
+
const projectPackagePath = regularUnaliasedFile(projectDir, join(projectDir, 'package.json'), 'consumer package manifest')
|
|
1061
|
+
const projectLockPath = regularUnaliasedFile(projectDir, join(projectDir, 'package-lock.json'), 'consumer package lock')
|
|
1062
|
+
const dsPackagePath = regularUnaliasedFile(projectDir, join(dsRoot, 'package.json'), 'installed design-system package manifest')
|
|
1063
|
+
const utilityRegistryPath = regularUnaliasedFile(projectDir, join(dsRoot, 'src/tokens/utility-registry.json'), 'installed canonical utility registry')
|
|
1064
|
+
const bomPath = regularUnaliasedFile(projectDir, join(forkRoot, 'consumer/lock.json'), 'installed consumer BOM')
|
|
1065
|
+
const corpusLockPath = regularUnaliasedFile(projectDir, join(forkRoot, 'governance.lock'), 'installed fork corpus lock')
|
|
1066
|
+
const manifestPath = regularUnaliasedFile(projectDir, join(forkRoot, 'manifest.json'), 'installed fork manifest')
|
|
1067
|
+
const projectPackage = JSON.parse(readFileSync(projectPackagePath, 'utf8'))
|
|
1068
|
+
const projectLock = JSON.parse(readFileSync(projectLockPath, 'utf8'))
|
|
1069
|
+
const dsPackage = JSON.parse(readFileSync(dsPackagePath, 'utf8'))
|
|
1070
|
+
const bom = JSON.parse(readFileSync(bomPath, 'utf8'))
|
|
1071
|
+
const corpusLock = JSON.parse(readFileSync(corpusLockPath, 'utf8'))
|
|
1072
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
|
|
1073
|
+
exactKeys(bom, ['$schema', 'authority', 'generatedBy', 'payload', 'providers', 'release', 'role', 'schemaVersion', 'upgradeProtocol', 'upgradeTrust'], 'installed consumer BOM')
|
|
1074
|
+
exactKeys(bom.release, ['designSystem', 'storybookConfig'], 'installed consumer BOM release')
|
|
1075
|
+
exactKeys(bom.upgradeProtocol, [
|
|
1076
|
+
'bootstrapEvidenceContract', 'candidateCodeExecutionAllowed',
|
|
1077
|
+
'controlPlaneUpdateEvidenceContract', 'controlPlaneUpdateMode', 'controlPlaneUpdatePlanner',
|
|
1078
|
+
'implementationSha256', 'legacyEntryMode', 'ordinaryUpgradeMode',
|
|
1079
|
+
'physicalPowerLossDurabilityClaimed', 'protectedBaseVerifier', 'protocolId', 'schemaSha256',
|
|
1080
|
+
'schemaVersion', 'specificationSha256',
|
|
1081
|
+
], 'installed consumer upgrade protocol')
|
|
1082
|
+
exactKeys(bom.payload, [
|
|
1083
|
+
'commonInstructionSha256', 'mergeSurfaceDigests', 'providerArtifactDigests',
|
|
1084
|
+
'discoveryPolicySha256', 'executionRuntimeSha256', 'forkCorpusLockSha256', 'governanceCheckSha256', 'managedFiles',
|
|
1085
|
+
'providerInventorySha256', 'providerLifecycleSha256', 'providerMaterializersSha256', 'requiredScripts', 'sharedSkillCount',
|
|
1086
|
+
'sharedSkillsSha256', 'utilityRegistrySha256',
|
|
1087
|
+
], 'installed consumer BOM payload')
|
|
1088
|
+
if (bom.$schema !== './lock.schema.json' || bom.schemaVersion !== 1 || bom.role !== 'template-consumer' || !bom.payload.managedFiles || typeof bom.payload.managedFiles !== 'object' || Array.isArray(bom.payload.managedFiles)) {
|
|
1089
|
+
throw new Error('installed consumer BOM identity is invalid')
|
|
1090
|
+
}
|
|
1091
|
+
if (
|
|
1092
|
+
bom.upgradeProtocol.schemaVersion !== 2
|
|
1093
|
+
|| bom.upgradeProtocol.protocolId !== 'protected-base-reconstruction-v2'
|
|
1094
|
+
|| bom.upgradeProtocol.legacyEntryMode !== 'one-time-reviewed-full-snapshot-pr'
|
|
1095
|
+
|| bom.upgradeProtocol.bootstrapEvidenceContract !== 'consumer-governance-bootstrap-readback-v1'
|
|
1096
|
+
|| bom.upgradeProtocol.controlPlaneUpdateMode !== 'recurring-reviewed-full-snapshot-pr'
|
|
1097
|
+
|| bom.upgradeProtocol.controlPlaneUpdateEvidenceContract !== 'consumer-governance-control-plane-update-readback-v1'
|
|
1098
|
+
|| bom.upgradeProtocol.controlPlaneUpdatePlanner !== 'infra/governance/bin/consumerctl.mjs'
|
|
1099
|
+
|| bom.upgradeProtocol.ordinaryUpgradeMode !== 'protected-base-disposable-reconstruction'
|
|
1100
|
+
|| bom.upgradeProtocol.protectedBaseVerifier !== 'scripts/verify-upgrade-evidence.mjs'
|
|
1101
|
+
|| bom.upgradeProtocol.candidateCodeExecutionAllowed !== false
|
|
1102
|
+
|| bom.upgradeProtocol.physicalPowerLossDurabilityClaimed !== false
|
|
1103
|
+
|| !SHA256.test(bom.upgradeProtocol.specificationSha256 || '')
|
|
1104
|
+
|| !SHA256.test(bom.upgradeProtocol.schemaSha256 || '')
|
|
1105
|
+
|| !SHA256.test(bom.upgradeProtocol.implementationSha256 || '')
|
|
1106
|
+
) throw new Error('installed consumer BOM lacks the protected-base reconstruction v2 boundary; use the reviewed full-snapshot bootstrap path')
|
|
1107
|
+
if (!EXACT_SEMVER.test(dsPackage.version || '') || bom?.release?.designSystem !== dsPackage.version) {
|
|
1108
|
+
throw new Error('installed consumer BOM is not bound to the installed design-system release')
|
|
1109
|
+
}
|
|
1110
|
+
if (!SHA256.test(bom?.payload?.forkCorpusLockSha256 || '') || bom.payload.forkCorpusLockSha256 !== fileSha256(corpusLockPath)) {
|
|
1111
|
+
throw new Error('installed consumer BOM does not bind the exact fork corpus lock')
|
|
1112
|
+
}
|
|
1113
|
+
if (!SHA256.test(bom?.payload?.utilityRegistrySha256 || '') || bom.payload.utilityRegistrySha256 !== fileSha256(utilityRegistryPath)) {
|
|
1114
|
+
throw new Error('installed consumer BOM does not bind the exact canonical utility registry')
|
|
1115
|
+
}
|
|
1116
|
+
exactKeys(corpusLock, ['_purpose', 'entries', 'hashAlgorithm', 'kind', 'schemaVersion'], 'installed fork corpus lock')
|
|
1117
|
+
if (corpusLock.schemaVersion !== 1 || corpusLock.kind !== 'fork-governance-corpus-lock' || corpusLock.hashAlgorithm !== 'sha256' || !Array.isArray(corpusLock.entries)) {
|
|
1118
|
+
throw new Error('installed fork corpus lock identity is invalid')
|
|
1119
|
+
}
|
|
1120
|
+
const entryFiles = []
|
|
1121
|
+
const entriesByFile = new Map()
|
|
1122
|
+
for (const [index, entry] of corpusLock.entries.entries()) {
|
|
1123
|
+
exactKeys(entry, ['classification', 'destination', 'file', 'schemaVersion', 'sha256', 'source'], `installed corpus entry[${index}]`)
|
|
1124
|
+
const relativePath = normalizeRepositoryRelative(entry.file, `installed corpus entry[${index}].file`)
|
|
1125
|
+
if (entry.schemaVersion !== 1 || !SHA256.test(entry.sha256 || '') || !safeRelative(forkRoot, relativePath)) throw new Error(`installed corpus entry is invalid:${relativePath}`)
|
|
1126
|
+
const body = regularUnaliasedFile(projectDir, join(forkRoot, relativePath), `installed corpus body ${relativePath}`)
|
|
1127
|
+
if (fileSha256(body) !== entry.sha256) throw new Error(`installed corpus body digest mismatch:${relativePath}`)
|
|
1128
|
+
entryFiles.push(relativePath)
|
|
1129
|
+
entriesByFile.set(relativePath, entry)
|
|
1130
|
+
}
|
|
1131
|
+
if (JSON.stringify(entryFiles) !== JSON.stringify([...new Set(entryFiles)].sort(compareUtf8Bytes))) {
|
|
1132
|
+
throw new Error('installed corpus entries must be sorted and unique')
|
|
1133
|
+
}
|
|
1134
|
+
const manifestEntry = corpusLock.entries.filter((entry) => entry.file === 'manifest.json')
|
|
1135
|
+
if (manifestEntry.length !== 1 || manifestEntry[0].sha256 !== fileSha256(manifestPath)) throw new Error('installed corpus lock does not bind exactly one manifest')
|
|
1136
|
+
const expectedManifestKeys = [
|
|
1137
|
+
'_generated', 'schemaVersion', 'kind', 'providerRegistrySchemaVersion', 'materializers', 'discoveryPolicy',
|
|
1138
|
+
'providerLifecycle', 'providerAdapters', 'authorityDecision', 'independentReview', 'launchers', 'hooks', 'ciReplay', 'skills', 'commands',
|
|
1139
|
+
'agents', 'providerSurfaces', 'codex', 'consumer',
|
|
1140
|
+
]
|
|
1141
|
+
exactKeys(manifest, expectedManifestKeys, 'installed fork manifest')
|
|
1142
|
+
if (manifest.schemaVersion !== 2 || manifest.kind !== 'provider-neutral-fork-manifest' || manifest.providerRegistrySchemaVersion !== 9) {
|
|
1143
|
+
throw new Error('installed fork manifest identity is invalid')
|
|
1144
|
+
}
|
|
1145
|
+
exactKeys(manifest.authorityDecision, [
|
|
1146
|
+
'schemaVersion', 'policySource', 'classifierSource', 'receiptContractSource', 'runner',
|
|
1147
|
+
'prepareArgv', 'verifyArgv', 'certificationClaim',
|
|
1148
|
+
], 'installed fork manifest authorityDecision')
|
|
1149
|
+
if (
|
|
1150
|
+
manifest.authorityDecision.schemaVersion !== 1
|
|
1151
|
+
|| manifest.authorityDecision.policySource !== 'AGENTS.md#canonical-decision-authority'
|
|
1152
|
+
|| manifest.authorityDecision.classifierSource !== 'launchers/approval-evidence.mjs'
|
|
1153
|
+
|| manifest.authorityDecision.receiptContractSource !== 'launchers/authority-decision-evidence.mjs'
|
|
1154
|
+
|| manifest.authorityDecision.runner !== 'launchers/product-hook-dispatch-runtime.mjs'
|
|
1155
|
+
|| JSON.stringify(manifest.authorityDecision.prepareArgv) !== JSON.stringify(['--authority-decision', 'prepare'])
|
|
1156
|
+
|| JSON.stringify(manifest.authorityDecision.verifyArgv) !== JSON.stringify(['--authority-decision', 'verify'])
|
|
1157
|
+
|| manifest.authorityDecision.certificationClaim !== 'structural-only-not-runtime-certified'
|
|
1158
|
+
) throw new Error('installed fork manifest authorityDecision projection is invalid')
|
|
1159
|
+
validateIndependentReviewProjection(manifest.independentReview)
|
|
1160
|
+
validateDiscoveryPolicy(manifest.discoveryPolicy, 'installed fork manifest discoveryPolicy')
|
|
1161
|
+
if (bom.payload.discoveryPolicySha256 !== sha256(JSON.stringify(manifest.discoveryPolicy))) {
|
|
1162
|
+
throw new Error('installed consumer BOM does not bind the discovery policy')
|
|
1163
|
+
}
|
|
1164
|
+
if (manifest.codex !== null) {
|
|
1165
|
+
exactKeys(manifest.codex, [
|
|
1166
|
+
'agentsMd', 'agentsSkills', 'compatibilityOnly', 'hookArtifact', 'hookDestination', 'hooksJson',
|
|
1167
|
+
'instructionDestination', 'skillArtifactRoot', 'skillDestination',
|
|
1168
|
+
], 'installed legacy Codex compatibility record')
|
|
1169
|
+
if (
|
|
1170
|
+
manifest.codex.compatibilityOnly !== true
|
|
1171
|
+
|| typeof manifest.codex.agentsMd !== 'string'
|
|
1172
|
+
|| typeof manifest.codex.instructionDestination !== 'string'
|
|
1173
|
+
|| !Array.isArray(manifest.codex.agentsSkills)
|
|
1174
|
+
|| !Array.isArray(manifest.codex.hooksJson)
|
|
1175
|
+
|| (manifest.codex.hookArtifact !== null && typeof manifest.codex.hookArtifact !== 'string')
|
|
1176
|
+
|| (manifest.codex.hookDestination !== null && typeof manifest.codex.hookDestination !== 'string')
|
|
1177
|
+
|| typeof manifest.codex.skillArtifactRoot !== 'string'
|
|
1178
|
+
|| typeof manifest.codex.skillDestination !== 'string'
|
|
1179
|
+
) throw new Error('installed legacy Codex compatibility record is invalid')
|
|
1180
|
+
}
|
|
1181
|
+
exactKeys(manifest.consumer, [
|
|
1182
|
+
'claudeMd', 'commonInstruction', 'compatibilityCategories', 'executionRuntime', 'governanceCheck', 'governanceLock', 'governanceLockSchema', 'launcherDestination', 'managedFiles', 'materialization',
|
|
1183
|
+
], 'installed fork manifest consumer')
|
|
1184
|
+
if (manifest.consumer.claudeMd !== null && typeof manifest.consumer.claudeMd !== 'string') {
|
|
1185
|
+
throw new Error('installed fork manifest legacy claudeMd compatibility alias is invalid')
|
|
1186
|
+
}
|
|
1187
|
+
if (!Array.isArray(manifest.consumer.compatibilityCategories)) throw new Error('installed compatibility category inventory must be an array')
|
|
1188
|
+
exactKeys(manifest.consumer.materialization, ['exactPaths', 'pathPrefixes'], 'installed fork manifest consumer materialization')
|
|
1189
|
+
if (!manifest.consumer.managedFiles || typeof manifest.consumer.managedFiles !== 'object' || Array.isArray(manifest.consumer.managedFiles)) {
|
|
1190
|
+
throw new Error('installed fork manifest managed-file map is invalid')
|
|
1191
|
+
}
|
|
1192
|
+
exactKeys(manifest.consumer.executionRuntime, [
|
|
1193
|
+
'engine', 'exactNpmVersion', 'minimumNodeVersion', 'nativeWindowsPolicy', 'requiredExecutables', 'requiredPathClasses', 'schemaVersion',
|
|
1194
|
+
'supportedHostPlatforms', 'windowsCompatibilityEnvironments',
|
|
1195
|
+
], 'installed consumer execution runtime')
|
|
1196
|
+
if (
|
|
1197
|
+
manifest.consumer.executionRuntime.schemaVersion !== 2
|
|
1198
|
+
|| manifest.consumer.executionRuntime.engine !== 'node-posix-toolchain-v2'
|
|
1199
|
+
|| !/^\d+\.\d+\.\d+$/.test(manifest.consumer.executionRuntime.minimumNodeVersion || '')
|
|
1200
|
+
|| !/^\d+\.\d+\.\d+$/.test(manifest.consumer.executionRuntime.exactNpmVersion || '')
|
|
1201
|
+
|| manifest.consumer.executionRuntime.nativeWindowsPolicy !== 'unsupported-fail-closed'
|
|
1202
|
+
|| JSON.stringify(manifest.consumer.executionRuntime.supportedHostPlatforms) !== JSON.stringify(['darwin', 'linux'])
|
|
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', 'perl', 'python3'])
|
|
1205
|
+
|| JSON.stringify(manifest.consumer.executionRuntime.requiredPathClasses) !== JSON.stringify(['plain', 'space-containing'])
|
|
1206
|
+
|| bom.payload.executionRuntimeSha256 !== sha256(JSON.stringify(manifest.consumer.executionRuntime))
|
|
1207
|
+
) throw new Error('installed consumer execution runtime contract is invalid or unbound')
|
|
1208
|
+
const requiredNodeRange = `>=${manifest.consumer.executionRuntime.minimumNodeVersion}`
|
|
1209
|
+
const lockedRoot = projectLock?.packages?.['']
|
|
1210
|
+
const lockedNpm = projectLock?.packages?.['node_modules/npm']
|
|
1211
|
+
if (
|
|
1212
|
+
projectPackage?.engines?.node !== requiredNodeRange
|
|
1213
|
+
|| projectPackage?.devDependencies?.npm !== manifest.consumer.executionRuntime.exactNpmVersion
|
|
1214
|
+
|| projectLock?.lockfileVersion !== 3
|
|
1215
|
+
|| lockedRoot?.engines?.node !== requiredNodeRange
|
|
1216
|
+
|| lockedRoot?.devDependencies?.npm !== manifest.consumer.executionRuntime.exactNpmVersion
|
|
1217
|
+
|| lockedNpm?.version !== manifest.consumer.executionRuntime.exactNpmVersion
|
|
1218
|
+
|| lockedNpm?.resolved !== `https://registry.npmjs.org/npm/-/npm-${manifest.consumer.executionRuntime.exactNpmVersion}.tgz`
|
|
1219
|
+
|| !/^sha512-[A-Za-z0-9+/]+={0,2}$/.test(lockedNpm?.integrity || '')
|
|
1220
|
+
) throw new Error('consumer package engines/npm lock differs from the installed execution runtime contract')
|
|
1221
|
+
if (!stableVersionAtLeast(process.versions.node, manifest.consumer.executionRuntime.minimumNodeVersion)) {
|
|
1222
|
+
throw new Error(`consumer refresh requires Node.js ${manifest.consumer.executionRuntime.minimumNodeVersion} or newer`)
|
|
1223
|
+
}
|
|
1224
|
+
const launcherDestination = normalizeRepositoryRelative(manifest.consumer.launcherDestination, 'installed shared launcher destination')
|
|
1225
|
+
if (!Array.isArray(manifest.launchers) || !manifest.launchers.length) throw new Error('installed fork manifest launcher inventory is empty')
|
|
1226
|
+
const launcherNames = manifest.launchers.map((name, index) => {
|
|
1227
|
+
if (typeof name !== 'string' || !/^[A-Za-z0-9._-]+\.(?:sh|mjs|py)$/.test(name)) {
|
|
1228
|
+
throw new Error(`installed fork manifest launcher[${index}] is invalid`)
|
|
1229
|
+
}
|
|
1230
|
+
return name
|
|
1231
|
+
})
|
|
1232
|
+
if (JSON.stringify(launcherNames) !== JSON.stringify([...new Set(launcherNames)].sort())) {
|
|
1233
|
+
throw new Error('installed fork manifest launchers must be sorted and unique')
|
|
1234
|
+
}
|
|
1235
|
+
if (!launcherNames.includes('independent-review.mjs')) throw new Error('installed fork manifest lacks the provider-neutral independent-review launcher')
|
|
1236
|
+
const assertLockedArtifact = (relativePath, label) => {
|
|
1237
|
+
const normalized = normalizeRepositoryRelative(relativePath, label)
|
|
1238
|
+
if (!safeRelative(forkRoot, normalized)) throw new Error(`${label} escapes fork corpus`)
|
|
1239
|
+
const absolute = join(forkRoot, normalized)
|
|
1240
|
+
assertNoSymlinkPath(projectDir, absolute, label, { allowMissing: false })
|
|
1241
|
+
const info = lstatSync(absolute)
|
|
1242
|
+
const files = []
|
|
1243
|
+
const visit = (path, rel) => {
|
|
1244
|
+
const childInfo = lstatSync(path)
|
|
1245
|
+
if (childInfo.isDirectory()) for (const name of readdirSync(path).sort()) visit(join(path, name), `${rel}/${name}`)
|
|
1246
|
+
else if (childInfo.isFile() && childInfo.nlink === 1) files.push(rel)
|
|
1247
|
+
else throw new Error(`${label} contains an unsafe filesystem entry`)
|
|
1248
|
+
}
|
|
1249
|
+
visit(absolute, normalized)
|
|
1250
|
+
if (!info.isFile() && !info.isDirectory()) throw new Error(`${label} is not a file or tree`)
|
|
1251
|
+
for (const file of files) if (!entriesByFile.has(file)) throw new Error(`${label} references unlocked corpus body:${file}`)
|
|
1252
|
+
return { normalized, files }
|
|
1253
|
+
}
|
|
1254
|
+
for (const [relativePath, label] of [
|
|
1255
|
+
[manifest.authorityDecision.classifierSource, 'installed authority decision classifier'],
|
|
1256
|
+
[manifest.authorityDecision.receiptContractSource, 'installed authority decision receipt contract'],
|
|
1257
|
+
[manifest.authorityDecision.runner, 'installed authority decision runner'],
|
|
1258
|
+
]) {
|
|
1259
|
+
const launcherName = relativePath.slice('launchers/'.length)
|
|
1260
|
+
if (!relativePath.startsWith('launchers/') || !launcherNames.includes(launcherName)) {
|
|
1261
|
+
throw new Error(`${label} is absent from the shared launcher inventory`)
|
|
1262
|
+
}
|
|
1263
|
+
assertLockedArtifact(relativePath, label)
|
|
1264
|
+
}
|
|
1265
|
+
const lifecycle = manifest.providerLifecycle
|
|
1266
|
+
assertLockedArtifact('review/provider-review-binding.mjs', 'installed provider review binding resolver')
|
|
1267
|
+
if (!lifecycle || typeof lifecycle !== 'object' || Array.isArray(lifecycle) || ![3, 4].includes(lifecycle.schemaVersion)) {
|
|
1268
|
+
throw new Error('installed provider lifecycle identity is invalid')
|
|
1269
|
+
}
|
|
1270
|
+
exactKeys(lifecycle, [
|
|
1271
|
+
'currentProviderInventorySha256', 'currentSnapshotSha256', 'immutableHead', 'ledgerSha256', 'previousSnapshotSha256',
|
|
1272
|
+
'releaseVersion', 'retiredProviders', 'schemaVersion',
|
|
1273
|
+
...(lifecycle.schemaVersion === 4 ? ['currentSnapshot', 'immutableHeadSnapshot'] : []),
|
|
1274
|
+
], 'installed provider lifecycle')
|
|
1275
|
+
if (
|
|
1276
|
+
lifecycle.releaseVersion !== dsPackage.version
|
|
1277
|
+
|| !SHA256.test(lifecycle.ledgerSha256 || '')
|
|
1278
|
+
|| !SHA256.test(lifecycle.currentSnapshotSha256 || '')
|
|
1279
|
+
|| !SHA256.test(lifecycle.currentProviderInventorySha256 || '')
|
|
1280
|
+
|| (lifecycle.previousSnapshotSha256 !== null && !SHA256.test(lifecycle.previousSnapshotSha256 || ''))
|
|
1281
|
+
|| !Array.isArray(lifecycle.retiredProviders)
|
|
1282
|
+
) throw new Error('installed provider lifecycle identity is invalid')
|
|
1283
|
+
exactKeys(lifecycle.immutableHead, ['providerInventorySha256', 'releaseVersion', 'snapshotSha256'], 'installed provider lifecycle immutable head')
|
|
1284
|
+
if (!EXACT_SEMVER.test(lifecycle.immutableHead.releaseVersion || '') || !SHA256.test(lifecycle.immutableHead.snapshotSha256 || '') || !SHA256.test(lifecycle.immutableHead.providerInventorySha256 || '')) {
|
|
1285
|
+
throw new Error('installed provider lifecycle immutable head is invalid')
|
|
1286
|
+
}
|
|
1287
|
+
validateAuthenticatedProviderLifecycle(lifecycle, { allowLegacyV3: true })
|
|
1288
|
+
const activeProviders = validateForkManifestProviderSurfaces(manifest)
|
|
1289
|
+
if (lifecycle.schemaVersion === 4) {
|
|
1290
|
+
const currentSnapshot = lifecycle.currentSnapshot
|
|
1291
|
+
const immutableHeadSnapshot = lifecycle.immutableHeadSnapshot
|
|
1292
|
+
assertSnapshotDiscoveryWithinPolicy(currentSnapshot, manifest.discoveryPolicy, 'installed provider lifecycle currentSnapshot')
|
|
1293
|
+
assertSnapshotDiscoveryWithinPolicy(immutableHeadSnapshot, manifest.discoveryPolicy, 'installed provider lifecycle immutableHeadSnapshot')
|
|
1294
|
+
if (JSON.stringify(snapshotActiveSurfaceView(currentSnapshot)) !== JSON.stringify(snapshotActiveSurfaceView({ providers: activeProviders }))) {
|
|
1295
|
+
throw new Error('installed provider lifecycle currentSnapshot provider ids/surfaces do not exactly match active providerSurfaces managedSurfaces')
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
const activeProviderById = new Map(activeProviders.map((provider) => [provider.id, provider]))
|
|
1299
|
+
const activeSurfaces = activeProviders.flatMap((provider) => provider.surfaces.map((surface) => ({ ...surface, providerId: provider.id })))
|
|
1300
|
+
const commonInstruction = manifest.consumer.commonInstruction
|
|
1301
|
+
const commonDestination = normalizeRepositoryRelative(commonInstruction.destination, 'installed common instruction destination')
|
|
1302
|
+
const commonArtifact = assertLockedArtifact(commonInstruction.artifact, 'installed common instruction artifact')
|
|
1303
|
+
if (
|
|
1304
|
+
commonArtifact.files.length !== 1
|
|
1305
|
+
|| bom.payload.commonInstructionSha256 !== entriesByFile.get(commonArtifact.normalized)?.sha256
|
|
1306
|
+
|| activeSurfaces.some((surface) => pathsOverlap(surface.path, commonDestination))
|
|
1307
|
+
) throw new Error('installed common instruction is unbound or incorrectly owned by a provider')
|
|
1308
|
+
const governanceCheck = assertLockedArtifact(manifest.consumer.governanceCheck, 'installed governance checker artifact')
|
|
1309
|
+
if (
|
|
1310
|
+
governanceCheck.files.length !== 1
|
|
1311
|
+
|| entriesByFile.get(governanceCheck.normalized)?.sha256 !== bom.payload.governanceCheckSha256
|
|
1312
|
+
) throw new Error('installed governance checker is not exactly bound by BOM/corpus lock')
|
|
1313
|
+
if (manifest.codex !== null) {
|
|
1314
|
+
const compatibility = manifest.codex
|
|
1315
|
+
const matchingSurfaces = Object.values(manifest.providerSurfaces).filter((surface) => (
|
|
1316
|
+
surface.generated
|
|
1317
|
+
&& surface.instructionArtifact === compatibility.agentsMd
|
|
1318
|
+
&& surface.instructionDestination === compatibility.instructionDestination
|
|
1319
|
+
&& surface.hookDestination === compatibility.hookDestination
|
|
1320
|
+
&& surface.skillDestination === compatibility.skillDestination
|
|
1321
|
+
&& JSON.stringify(surface.skills) === JSON.stringify(compatibility.agentsSkills)
|
|
1322
|
+
))
|
|
1323
|
+
if (matchingSurfaces.length !== 1) throw new Error('legacy Codex compatibility record does not bind exactly one active provider surface')
|
|
1324
|
+
assertLockedArtifact(compatibility.agentsMd, 'legacy Codex instruction alias source')
|
|
1325
|
+
if (compatibility.hookArtifact !== null) assertLockedArtifact(compatibility.hookArtifact, 'legacy Codex hook alias')
|
|
1326
|
+
for (const name of compatibility.agentsSkills) {
|
|
1327
|
+
assertLockedArtifact(`${compatibility.skillArtifactRoot}/${name}`, `legacy Codex skill alias ${name}`)
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
const compatibilityCategories = manifest.consumer.compatibilityCategories.map((category, index) => {
|
|
1331
|
+
const categoryLabel = `installed compatibility category[${index}]`
|
|
1332
|
+
exactKeys(category, ['artifactRoot', 'destinationRoot', 'entries', 'name', 'providerId', 'schemaVersion'], categoryLabel)
|
|
1333
|
+
if (
|
|
1334
|
+
category.schemaVersion !== 1
|
|
1335
|
+
|| !['agents', 'commands'].includes(category.name)
|
|
1336
|
+
|| !/^[a-z][a-z0-9-]*$/.test(category.providerId || '')
|
|
1337
|
+
) throw new Error(`${categoryLabel} identity is invalid`)
|
|
1338
|
+
const artifactRoot = normalizeRepositoryRelative(category.artifactRoot, `${categoryLabel}.artifactRoot`)
|
|
1339
|
+
const destinationRoot = normalizeRepositoryRelative(category.destinationRoot, `${categoryLabel}.destinationRoot`)
|
|
1340
|
+
const entries = sortedUniqueStrings(category.entries, `${categoryLabel}.entries`)
|
|
1341
|
+
const owner = activeProviderById.get(category.providerId)
|
|
1342
|
+
if (!owner) throw new Error(`${categoryLabel} owner is not an active generated provider`)
|
|
1343
|
+
if (JSON.stringify(manifest[category.name]) !== JSON.stringify(entries)) throw new Error(`${categoryLabel} differs from its legacy manifest alias`)
|
|
1344
|
+
for (const entry of entries) {
|
|
1345
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_.@-]*\.md$/.test(entry)) throw new Error(`${categoryLabel} entry is unsafe:${entry}`)
|
|
1346
|
+
const destination = `${destinationRoot}/${entry}`
|
|
1347
|
+
if (!owner.surfaces.some((surface) => surface.path === destination && surface.kind === 'file')) {
|
|
1348
|
+
throw new Error(`${categoryLabel} destination is absent from provider lifecycle ownership:${destination}`)
|
|
1349
|
+
}
|
|
1350
|
+
assertLockedArtifact(`${artifactRoot}/${entry}`, `${categoryLabel} artifact ${entry}`)
|
|
1351
|
+
if (!manifest.consumer.materialization.exactPaths.includes(destination)) throw new Error(`${categoryLabel} destination is absent from exact materialization:${destination}`)
|
|
1352
|
+
}
|
|
1353
|
+
return { ...category, artifactRoot, destinationRoot, entries }
|
|
1354
|
+
})
|
|
1355
|
+
if (
|
|
1356
|
+
JSON.stringify(compatibilityCategories.map((category) => category.name))
|
|
1357
|
+
!== JSON.stringify([...new Set(compatibilityCategories.map((category) => category.name))].sort())
|
|
1358
|
+
) throw new Error('installed compatibility categories must be sorted and unique by name')
|
|
1359
|
+
for (const name of ['agents', 'commands']) {
|
|
1360
|
+
if (!compatibilityCategories.some((category) => category.name === name) && (manifest[name] || []).length) {
|
|
1361
|
+
throw new Error(`legacy ${name} inventory exists without an active compatibility category owner`)
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
if (activeSurfaces.some((surface) => pathsOverlap(surface.path, launcherDestination))) {
|
|
1365
|
+
throw new Error(`shared launcher destination is owned by a provider lifecycle surface:${launcherDestination}`)
|
|
1366
|
+
}
|
|
1367
|
+
const retiredIds = []
|
|
1368
|
+
const retiredPaths = []
|
|
1369
|
+
for (const [index, retirement] of lifecycle.retiredProviders.entries()) {
|
|
1370
|
+
exactKeys(retirement, [
|
|
1371
|
+
'discovery', 'id', 'priorProviderSha256', 'priorReleaseVersion', 'priorSnapshotSha256',
|
|
1372
|
+
'reasonCode', 'replacementProviderId', 'retiredInVersion', 'surfaces',
|
|
1373
|
+
...(Object.hasOwn(retirement || {}, 'transfers') ? ['transfers'] : []),
|
|
1374
|
+
], `installed retirement[${index}]`)
|
|
1375
|
+
const transfers = retirement.transfers || []
|
|
1376
|
+
if (
|
|
1377
|
+
!/^[a-z][a-z0-9-]*$/.test(retirement.id || '')
|
|
1378
|
+
|| !EXACT_SEMVER.test(retirement.priorReleaseVersion || '')
|
|
1379
|
+
|| !EXACT_SEMVER.test(retirement.retiredInVersion || '')
|
|
1380
|
+
|| !SHA256.test(retirement.priorSnapshotSha256 || '')
|
|
1381
|
+
|| !SHA256.test(retirement.priorProviderSha256 || '')
|
|
1382
|
+
|| !Array.isArray(retirement.surfaces)
|
|
1383
|
+
|| (Object.hasOwn(retirement, 'transfers') && (!Array.isArray(transfers) || !transfers.length))
|
|
1384
|
+
|| (!retirement.surfaces.length && !transfers.length)
|
|
1385
|
+
) throw new Error(`installed retirement proof is invalid:${retirement?.id || index}`)
|
|
1386
|
+
validateDiscovery(retirement.discovery, `installed retirement ${retirement.id}.discovery`)
|
|
1387
|
+
const surfaces = retirement.surfaces.map((surface, surfaceIndex) => validateRetiredSurface(surface, `installed retirement ${retirement.id}.surfaces[${surfaceIndex}]`))
|
|
1388
|
+
if (surfaces.some((surface) => pathsOverlap(surface.path, commonDestination))) {
|
|
1389
|
+
throw new Error(`${retirement.id}:provider tombstone may not delete the common instruction authority`)
|
|
1390
|
+
}
|
|
1391
|
+
if (JSON.stringify(surfaces) !== JSON.stringify([...surfaces].sort((left, right) => compareUtf8Bytes(left.path, right.path)))) throw new Error(`${retirement.id}:retired surfaces must be sorted`)
|
|
1392
|
+
const transferred = transfers.map((surface, surfaceIndex) => validateSurface(surface, `installed retirement ${retirement.id}.transfers[${surfaceIndex}]`))
|
|
1393
|
+
if (JSON.stringify(transferred) !== JSON.stringify([...transferred].sort((left, right) => compareUtf8Bytes(left.path, right.path)))) throw new Error(`${retirement.id}:transferred surfaces must be sorted`)
|
|
1394
|
+
const partition = [...surfaces, ...transferred].map(({ path, kind }) => ({ path, kind }))
|
|
1395
|
+
.sort((left, right) => compareUtf8Bytes(left.path, right.path))
|
|
1396
|
+
if (partition.length !== new Set(partition.map((surface) => portableRepositoryPathKey(surface.path))).size) throw new Error(`${retirement.id}:retirement deletion/transfer partition contains duplicate paths`)
|
|
1397
|
+
const priorProvider = {
|
|
1398
|
+
id: retirement.id,
|
|
1399
|
+
discovery: retirement.discovery,
|
|
1400
|
+
surfaces: partition,
|
|
1401
|
+
}
|
|
1402
|
+
if (sha256(JSON.stringify(priorProvider)) !== retirement.priorProviderSha256) throw new Error(`${retirement.id}:retirement does not bind its prior provider inventory`)
|
|
1403
|
+
const replacement = retirement.replacementProviderId === null ? null : activeProviderById.get(retirement.replacementProviderId)
|
|
1404
|
+
if (retirement.replacementProviderId !== null && (!replacement || retirement.replacementProviderId === retirement.id)) {
|
|
1405
|
+
throw new Error(`${retirement.id}:replacementProviderId must name a distinct active current provider`)
|
|
1406
|
+
}
|
|
1407
|
+
if (!replacement && transferred.length) throw new Error(`${retirement.id}:null replacementProviderId cannot transfer surfaces`)
|
|
1408
|
+
for (const surface of surfaces) {
|
|
1409
|
+
if (activeSurfaces.some((candidate) => pathsOverlap(candidate.path, surface.path))) {
|
|
1410
|
+
throw new Error(`${retirement.id}:deletion tombstone remains owned by an active provider:${surface.path}`)
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
for (const surface of transferred) {
|
|
1414
|
+
if (!replacement.surfaces.some((candidate) => candidate.path === surface.path && candidate.kind === surface.kind)) {
|
|
1415
|
+
throw new Error(`${retirement.id}:transfer is not exactly owned by replacement ${retirement.replacementProviderId}:${surface.path}`)
|
|
1416
|
+
}
|
|
1417
|
+
if (activeSurfaces.some((candidate) => candidate.providerId !== replacement.id && pathsOverlap(candidate.path, surface.path))) {
|
|
1418
|
+
throw new Error(`${retirement.id}:transfer is also owned by a non-replacement provider:${surface.path}`)
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
retiredIds.push(retirement.id)
|
|
1422
|
+
retiredPaths.push(...surfaces.map((surface) => surface.path))
|
|
1423
|
+
}
|
|
1424
|
+
if (JSON.stringify(retiredIds) !== JSON.stringify([...new Set(retiredIds)].sort()) || retiredPaths.length !== new Set(retiredPaths.map(portableRepositoryPathKey)).size) {
|
|
1425
|
+
throw new Error('installed retirement ids and paths must be globally unique and sorted')
|
|
1426
|
+
}
|
|
1427
|
+
if (bom.payload.providerLifecycleSha256 !== sha256(JSON.stringify(lifecycle))) throw new Error('installed consumer BOM does not bind the provider lifecycle')
|
|
1428
|
+
if (bom.payload.providerInventorySha256 !== sha256(JSON.stringify(manifest.providerSurfaces))) throw new Error('installed consumer BOM does not bind provider surfaces')
|
|
1429
|
+
if (bom.payload.providerMaterializersSha256 !== sha256(JSON.stringify(manifest.materializers))) throw new Error('installed consumer BOM does not bind provider materializers')
|
|
1430
|
+
const activeIds = activeProviders.map((provider) => provider.id)
|
|
1431
|
+
if (activeIds.some((id) => retiredIds.includes(id))) throw new Error('installed provider lifecycle reactivates a retired provider')
|
|
1432
|
+
if (JSON.stringify(Object.keys(manifest.consumer.managedFiles).sort()) !== JSON.stringify(Object.keys(bom.payload.managedFiles).sort())) {
|
|
1433
|
+
throw new Error('installed managed-file artifact map differs from the consumer BOM')
|
|
1434
|
+
}
|
|
1435
|
+
for (const [destination, artifact] of Object.entries(manifest.consumer.managedFiles)) {
|
|
1436
|
+
normalizeRepositoryRelative(destination, `managed-file destination ${destination}`)
|
|
1437
|
+
const locked = assertLockedArtifact(artifact, `managed-file artifact ${artifact}`)
|
|
1438
|
+
if (locked.files.length !== 1 || entriesByFile.get(locked.normalized)?.sha256 !== bom.payload.managedFiles[destination]) {
|
|
1439
|
+
throw new Error(`managed-file artifact is not exactly bound by BOM/corpus lock:${destination}`)
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
for (const [providerId, surface] of Object.entries(manifest.providerSurfaces)) {
|
|
1443
|
+
if (!surface.generated) continue
|
|
1444
|
+
assertLockedArtifact(surface.instructionArtifact, `${providerId} instruction artifact`)
|
|
1445
|
+
if (surface.hookArtifact) assertLockedArtifact(surface.hookArtifact, `${providerId} hook artifact`)
|
|
1446
|
+
for (const name of surface.skills) assertLockedArtifact(`${surface.skillArtifactRoot}/${name}`, `${providerId} skill artifact ${name}`)
|
|
1447
|
+
for (const tree of surface.trees) {
|
|
1448
|
+
const locked = assertLockedArtifact(tree.artifactRoot, `${providerId} product managed tree ${tree.name}`)
|
|
1449
|
+
if (!locked.files.length) throw new Error(`${providerId} product managed tree is empty or unlocked:${tree.name}`)
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
const lockedTreeDigest = (prefix) => sha256(corpusLock.entries
|
|
1453
|
+
.filter((entry) => entry.file.startsWith(`${prefix}/`))
|
|
1454
|
+
.map((entry) => `${entry.file}:${entry.sha256}`)
|
|
1455
|
+
.join('\n') + '\n')
|
|
1456
|
+
const sharedSkillEntries = corpusLock.entries
|
|
1457
|
+
.filter((entry) => entry.file.startsWith('skills/'))
|
|
1458
|
+
.map((entry) => `${entry.file}:${entry.sha256}`)
|
|
1459
|
+
.sort()
|
|
1460
|
+
const lockedSharedSkillNames = [...new Set(corpusLock.entries
|
|
1461
|
+
.filter((entry) => entry.file.startsWith('skills/'))
|
|
1462
|
+
.map((entry) => entry.file.split('/')[1]))]
|
|
1463
|
+
.sort()
|
|
1464
|
+
if (
|
|
1465
|
+
!Array.isArray(manifest.skills)
|
|
1466
|
+
|| JSON.stringify(manifest.skills) !== JSON.stringify([...new Set(manifest.skills)].sort())
|
|
1467
|
+
|| manifest.skills.some((name) => !/^[a-z][a-z0-9-]*$/.test(name))
|
|
1468
|
+
|| JSON.stringify(manifest.skills) !== JSON.stringify(lockedSharedSkillNames)
|
|
1469
|
+
|| bom.payload.sharedSkillCount !== manifest.skills.length
|
|
1470
|
+
|| bom.payload.sharedSkillsSha256 !== sha256(`${sharedSkillEntries.join('\n')}\n`)
|
|
1471
|
+
) throw new Error('installed shared skill inventory/digest is invalid')
|
|
1472
|
+
const providerIds = Object.keys(manifest.providerSurfaces).sort()
|
|
1473
|
+
if (JSON.stringify(Object.keys(bom.payload.providerArtifactDigests || {}).sort()) !== JSON.stringify(providerIds)) {
|
|
1474
|
+
throw new Error('installed provider artifact digest map differs from the provider inventory')
|
|
1475
|
+
}
|
|
1476
|
+
for (const [providerId, surface] of Object.entries(manifest.providerSurfaces).sort(([a], [b]) => compareUtf8Bytes(a, b))) {
|
|
1477
|
+
const binding = exactKeys(bom.payload.providerArtifactDigests[providerId], [
|
|
1478
|
+
'hookSha256', 'instructionSha256', 'skillsSha256', 'treeDigests',
|
|
1479
|
+
], `installed provider artifact digest ${providerId}`)
|
|
1480
|
+
exactKeys(binding.treeDigests, surface.trees.map((tree) => tree.name), `installed provider tree digest ${providerId}`)
|
|
1481
|
+
const expected = {
|
|
1482
|
+
instructionSha256: surface.instructionArtifact ? fileSha256(join(forkRoot, surface.instructionArtifact)) : null,
|
|
1483
|
+
hookSha256: surface.hookArtifact ? fileSha256(join(forkRoot, surface.hookArtifact)) : null,
|
|
1484
|
+
skillsSha256: surface.skillArtifactRoot ? lockedTreeDigest(surface.skillArtifactRoot) : null,
|
|
1485
|
+
treeDigests: Object.fromEntries(surface.trees.map((tree) => [tree.name, lockedTreeDigest(tree.artifactRoot)])),
|
|
1486
|
+
}
|
|
1487
|
+
if (JSON.stringify(binding) !== JSON.stringify(expected)) throw new Error(`${providerId}:installed provider artifact digest binding is invalid`)
|
|
1488
|
+
}
|
|
1489
|
+
const mergePolicies = validateCanonicalSettingsWiring(
|
|
1490
|
+
JSON.parse(readFileSync(join(forkRoot, 'launchers/settings-hooks.json'), 'utf8')),
|
|
1491
|
+
manifest,
|
|
1492
|
+
)
|
|
1493
|
+
const expectedMergeDigests = Object.fromEntries(Object.entries(mergePolicies.surfaces)
|
|
1494
|
+
.sort(([a], [b]) => compareUtf8Bytes(a, b))
|
|
1495
|
+
.map(([providerId, policy]) => [providerId, fileSha256(join(forkRoot, policy.hookArtifact))]))
|
|
1496
|
+
if (JSON.stringify(bom.payload.mergeSurfaceDigests) !== JSON.stringify(expectedMergeDigests)) {
|
|
1497
|
+
throw new Error('installed merge-surface digest map differs from the locked merge policies')
|
|
1498
|
+
}
|
|
1499
|
+
const exactMaterialization = manifest.consumer.materialization.exactPaths
|
|
1500
|
+
if (!Array.isArray(exactMaterialization)) throw new Error('consumer exact materialization inventory must be an array')
|
|
1501
|
+
const prefixMaterialization = manifest.consumer.materialization.pathPrefixes
|
|
1502
|
+
if (!Array.isArray(prefixMaterialization)) throw new Error('consumer prefix materialization inventory must be an array')
|
|
1503
|
+
const generatedSurfaces = Object.entries(manifest.providerSurfaces)
|
|
1504
|
+
.filter(([, surface]) => surface.generated)
|
|
1505
|
+
.sort(([left], [right]) => compareUtf8Bytes(left, right))
|
|
1506
|
+
const exactMaterializationClaims = [
|
|
1507
|
+
...Object.keys(manifest.consumer.managedFiles).map((destination) => ({
|
|
1508
|
+
owner: `consumer-managed-file:${destination}`,
|
|
1509
|
+
path: normalizeRepositoryRelative(destination, `managed-file materialization destination ${destination}`),
|
|
1510
|
+
})),
|
|
1511
|
+
...CONSUMER_CONTROL_PLANE_PATHS.map((path) => ({ owner: `consumer-control-plane:${path}`, path })),
|
|
1512
|
+
{ owner: 'common-instruction', path: commonDestination },
|
|
1513
|
+
...generatedSurfaces.flatMap(([providerId, surface]) => {
|
|
1514
|
+
const instructionMaterializer = resolveManifestMaterializer(
|
|
1515
|
+
manifest,
|
|
1516
|
+
'instructionViews',
|
|
1517
|
+
surface.instructionMaterializerId,
|
|
1518
|
+
`${providerId}.instructionMaterializerId`,
|
|
1519
|
+
)
|
|
1520
|
+
return [
|
|
1521
|
+
...(instructionMaterializer.engine === 'shared-instruction-v1' ? [] : [{
|
|
1522
|
+
owner: `${providerId}:instruction`,
|
|
1523
|
+
path: normalizeRepositoryRelative(surface.instructionDestination, `${providerId} instruction materialization destination`),
|
|
1524
|
+
}]),
|
|
1525
|
+
...(surface.hookDestination === null ? [] : [{
|
|
1526
|
+
owner: `${providerId}:hook`,
|
|
1527
|
+
path: normalizeRepositoryRelative(surface.hookDestination, `${providerId} hook materialization destination`),
|
|
1528
|
+
}]),
|
|
1529
|
+
]
|
|
1530
|
+
}),
|
|
1531
|
+
...launcherNames.map((name) => ({ owner: `shared-launcher:${name}`, path: `${launcherDestination}/${name}` })),
|
|
1532
|
+
...compatibilityCategories.flatMap((category) => (
|
|
1533
|
+
category.entries.map((entry) => ({
|
|
1534
|
+
owner: `compatibility:${category.name}:${entry}`,
|
|
1535
|
+
path: `${category.destinationRoot}/${entry}`,
|
|
1536
|
+
}))
|
|
1537
|
+
)),
|
|
1538
|
+
]
|
|
1539
|
+
const prefixMaterializationClaims = generatedSurfaces.flatMap(([providerId, surface]) => [
|
|
1540
|
+
...surface.skills.map((name) => ({
|
|
1541
|
+
owner: `${providerId}:skill:${name}`,
|
|
1542
|
+
path: `${normalizeRepositoryRelative(surface.skillDestination, `${providerId} skill materialization destination`)}/${name}`,
|
|
1543
|
+
})),
|
|
1544
|
+
...surface.trees.map((tree) => ({
|
|
1545
|
+
owner: `${providerId}:tree:${tree.name}`,
|
|
1546
|
+
path: normalizeRepositoryRelative(tree.destination, `${providerId} tree materialization destination ${tree.name}`),
|
|
1547
|
+
})),
|
|
1548
|
+
])
|
|
1549
|
+
const ownerClaims = [
|
|
1550
|
+
...exactMaterializationClaims.filter((claim) => !claim.owner.startsWith('shared-launcher:')),
|
|
1551
|
+
{ owner: 'shared-launchers', path: launcherDestination },
|
|
1552
|
+
...prefixMaterializationClaims,
|
|
1553
|
+
]
|
|
1554
|
+
.sort((left, right) => (
|
|
1555
|
+
compareUtf8Bytes(left.path, right.path) || compareUtf8Bytes(left.owner, right.owner)
|
|
1556
|
+
))
|
|
1557
|
+
for (const claim of ownerClaims.filter((item) => !item.owner.startsWith('consumer-control-plane:'))) {
|
|
1558
|
+
const controlPlaneAuthority = consumerControlPlaneAuthority(claim.path)
|
|
1559
|
+
if (controlPlaneAuthority) {
|
|
1560
|
+
throw new Error(
|
|
1561
|
+
'non-provider materialization authority overlaps consumer control plane:'
|
|
1562
|
+
+ `${claim.owner}:${claim.path} <> ${controlPlaneAuthority}`,
|
|
1563
|
+
)
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
for (let left = 0; left < ownerClaims.length; left += 1) for (let right = left + 1; right < ownerClaims.length; right += 1) {
|
|
1567
|
+
if (repositoryPathsOverlap(ownerClaims[left].path, ownerClaims[right].path)) {
|
|
1568
|
+
throw new Error(
|
|
1569
|
+
'non-provider materialization authorities overlap:'
|
|
1570
|
+
+ `${ownerClaims[left].owner}:${ownerClaims[left].path}`
|
|
1571
|
+
+ ` <> ${ownerClaims[right].owner}:${ownerClaims[right].path}`,
|
|
1572
|
+
)
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
const expectedExactMaterialization = [...new Set(exactMaterializationClaims.map((claim) => claim.path))].sort()
|
|
1576
|
+
const canonicalExactMaterialization = exactMaterialization.map((path, index) => {
|
|
1577
|
+
const normalized = normalizeRepositoryRelative(path, `consumer exact materialization[${index}]`)
|
|
1578
|
+
if (path !== normalized) throw new Error(`consumer exact materialization path is not canonical:${path}`)
|
|
1579
|
+
return normalized
|
|
1580
|
+
})
|
|
1581
|
+
if (JSON.stringify(canonicalExactMaterialization) !== JSON.stringify(expectedExactMaterialization)) {
|
|
1582
|
+
throw new Error('consumer exact materialization inventory must be sorted, unique, canonical, and exactly derived from authenticated corpus')
|
|
1583
|
+
}
|
|
1584
|
+
const expectedPrefixMaterialization = [...new Set(prefixMaterializationClaims.map((claim) => `${claim.path}/`))].sort()
|
|
1585
|
+
const canonicalPrefixMaterialization = prefixMaterialization.map((prefix, index) => {
|
|
1586
|
+
if (typeof prefix !== 'string') throw new Error(`consumer prefix materialization[${index}] must be a string`)
|
|
1587
|
+
const normalized = `${normalizeRepositoryRelative(prefix, `consumer prefix materialization[${index}]`)}/`
|
|
1588
|
+
if (prefix !== normalized) throw new Error(`consumer prefix materialization path is not canonical:${prefix}`)
|
|
1589
|
+
return normalized
|
|
1590
|
+
})
|
|
1591
|
+
if (JSON.stringify(canonicalPrefixMaterialization) !== JSON.stringify(expectedPrefixMaterialization)) {
|
|
1592
|
+
throw new Error('consumer prefix materialization inventory must be sorted, unique, canonical, and exactly derived from authenticated corpus')
|
|
1593
|
+
}
|
|
1594
|
+
for (const name of launcherNames) {
|
|
1595
|
+
assertLockedArtifact(`launchers/${name}`, `shared launcher artifact ${name}`)
|
|
1596
|
+
}
|
|
1597
|
+
return {
|
|
1598
|
+
projectDir,
|
|
1599
|
+
forkRoot,
|
|
1600
|
+
manifest,
|
|
1601
|
+
activeProviders,
|
|
1602
|
+
bom,
|
|
1603
|
+
corpusLock,
|
|
1604
|
+
evidence: {
|
|
1605
|
+
releaseVersion: dsPackage.version,
|
|
1606
|
+
consumerBomSha256: fileSha256(bomPath),
|
|
1607
|
+
forkCorpusLockSha256: fileSha256(corpusLockPath),
|
|
1608
|
+
manifestSha256: fileSha256(manifestPath),
|
|
1609
|
+
providerLifecycleSha256: bom.payload.providerLifecycleSha256,
|
|
1610
|
+
},
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
export function collectProviderMutationPaths(manifest, authenticatedActiveProviders = null) {
|
|
1615
|
+
const records = []
|
|
1616
|
+
for (const provider of authenticatedActiveProviders || validateForkManifestProviderSurfaces(manifest)) records.push(...provider.surfaces)
|
|
1617
|
+
for (const retirement of manifest?.providerLifecycle?.retiredProviders || []) {
|
|
1618
|
+
for (const surface of retirement.surfaces || []) {
|
|
1619
|
+
const validated = validateRetiredSurface(surface, `${retirement.id} retired mutation path`)
|
|
1620
|
+
records.push({ path: validated.path, kind: validated.kind })
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
const byPath = new Map()
|
|
1624
|
+
for (const record of records) {
|
|
1625
|
+
const key = portableRepositoryPathKey(record.path)
|
|
1626
|
+
const prior = byPath.get(key)
|
|
1627
|
+
if (prior && (prior.kind !== record.kind || prior.path !== record.path)) throw new Error(`provider mutation path has conflicting portable aliases:${prior.path}:${record.path}`)
|
|
1628
|
+
byPath.set(key, record)
|
|
1629
|
+
}
|
|
1630
|
+
return [...byPath.values()].map(({ path, kind }) => ({ path, kind }))
|
|
1631
|
+
.sort((left, right) => compareUtf8Bytes(left.path, right.path))
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
const pathsOverlap = repositoryPathsOverlap
|
|
1635
|
+
export const CONSUMER_CONTROL_PLANE_PATHS = Object.freeze([
|
|
1636
|
+
'package.json',
|
|
1637
|
+
'package-lock.json',
|
|
1638
|
+
'npm-shrinkwrap.json',
|
|
1639
|
+
'governance/lock.json',
|
|
1640
|
+
'governance/lock.schema.json',
|
|
1641
|
+
'scripts/governance-check.mjs',
|
|
1642
|
+
])
|
|
1643
|
+
export function consumerControlPlaneAuthority(providerPath) {
|
|
1644
|
+
const normalized = normalizeRepositoryRelative(providerPath, 'provider control-plane collision path')
|
|
1645
|
+
const rootSegment = normalized.split('/')[0]
|
|
1646
|
+
if (/^package[^/]*\.json$/.test(portableRepositoryPathKey(rootSegment))) return rootSegment
|
|
1647
|
+
return CONSUMER_CONTROL_PLANE_PATHS.find((reserved) => pathsOverlap(normalized, reserved)) || null
|
|
1648
|
+
}
|
|
1649
|
+
const validatedProviderTransitions = new WeakSet()
|
|
1650
|
+
const deepFreeze = (value) => {
|
|
1651
|
+
if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value
|
|
1652
|
+
for (const child of Object.values(value)) deepFreeze(child)
|
|
1653
|
+
return Object.freeze(value)
|
|
1654
|
+
}
|
|
1655
|
+
const certifyProviderTransition = (transition) => {
|
|
1656
|
+
deepFreeze(transition)
|
|
1657
|
+
validatedProviderTransitions.add(transition)
|
|
1658
|
+
return transition
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
const nonProviderTransitionAuthority = (corpus, label) => {
|
|
1662
|
+
const consumer = corpus?.manifest?.consumer
|
|
1663
|
+
if (!consumer || typeof consumer !== 'object' || Array.isArray(consumer)) {
|
|
1664
|
+
throw new Error(`${label} consumer authority is missing`)
|
|
1665
|
+
}
|
|
1666
|
+
const launcherDestination = normalizeRepositoryRelative(
|
|
1667
|
+
consumer.launcherDestination,
|
|
1668
|
+
`${label} shared launcher destination`,
|
|
1669
|
+
)
|
|
1670
|
+
const discoveryPolicy = structuredClone(validateDiscoveryPolicy(corpus?.manifest?.discoveryPolicy, `${label} discovery policy`))
|
|
1671
|
+
const launchers = sortedUniqueStrings(corpus?.manifest?.launchers, `${label} shared launcher inventory`)
|
|
1672
|
+
for (const [index, name] of launchers.entries()) {
|
|
1673
|
+
if (!/^[A-Za-z0-9._-]+\.(?:sh|mjs|py)$/.test(name)) throw new Error(`${label} shared launcher[${index}] is invalid`)
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
const managedFiles = consumer.managedFiles
|
|
1677
|
+
if (!managedFiles || typeof managedFiles !== 'object' || Array.isArray(managedFiles)) {
|
|
1678
|
+
throw new Error(`${label} consumer managed-file authority must be an object`)
|
|
1679
|
+
}
|
|
1680
|
+
const managedDestinations = Object.keys(managedFiles).map((destination) => {
|
|
1681
|
+
const normalized = normalizeRepositoryRelative(destination, `${label} consumer managed-file destination`)
|
|
1682
|
+
if (normalized !== destination || typeof managedFiles[destination] !== 'string' || !managedFiles[destination]) {
|
|
1683
|
+
throw new Error(`${label} consumer managed-file mapping is invalid:${destination}`)
|
|
1684
|
+
}
|
|
1685
|
+
return normalized
|
|
1686
|
+
}).sort()
|
|
1687
|
+
if (managedDestinations.length !== new Set(managedDestinations.map(portableRepositoryPathKey)).size) {
|
|
1688
|
+
throw new Error(`${label} consumer managed-file authority has portable aliases`)
|
|
1689
|
+
}
|
|
1690
|
+
// The authenticated shared-launcher substrate reserves its parent governance namespace too.
|
|
1691
|
+
// This lets a later release add exact-hash, non-executable governance data beside governance/bin
|
|
1692
|
+
// without claiming a product-owned root or requiring an artificial bootstrap. The protected
|
|
1693
|
+
// evidence verifier still rejects every governance/bin/** ordinary-upgrade mutation.
|
|
1694
|
+
const managedNamespaceRoots = [
|
|
1695
|
+
...managedDestinations.filter((destination) => destination.includes('/'))
|
|
1696
|
+
.map((destination) => destination.split('/').slice(0, -1).join('/')),
|
|
1697
|
+
...(launcherDestination.includes('/') ? [launcherDestination.split('/').slice(0, -1).join('/')] : []),
|
|
1698
|
+
]
|
|
1699
|
+
.sort((left, right) => (
|
|
1700
|
+
left.split('/').length - right.split('/').length || compareUtf8Bytes(left, right)
|
|
1701
|
+
))
|
|
1702
|
+
.filter((candidate, index, values) => !values.slice(0, index).some((root) => {
|
|
1703
|
+
const candidateKey = portableRepositoryPathKey(candidate)
|
|
1704
|
+
const rootKey = portableRepositoryPathKey(root)
|
|
1705
|
+
return candidateKey === rootKey || candidateKey.startsWith(`${rootKey}/`)
|
|
1706
|
+
}))
|
|
1707
|
+
.sort()
|
|
1708
|
+
|
|
1709
|
+
const requiredScripts = corpus?.bom?.payload?.requiredScripts
|
|
1710
|
+
mergeRequiredPackageScripts({ scripts: {} }, requiredScripts)
|
|
1711
|
+
const requiredScriptNames = Object.keys(requiredScripts)
|
|
1712
|
+
const exactMaterialization = sortedUniqueStrings(
|
|
1713
|
+
consumer?.materialization?.exactPaths,
|
|
1714
|
+
`${label} exact materialization inventory`,
|
|
1715
|
+
).map((path, index) => normalizeRepositoryRelative(path, `${label} exact materialization[${index}]`))
|
|
1716
|
+
const exactMaterializationPaths = new Set(exactMaterialization)
|
|
1717
|
+
const ownedMaterialization = [
|
|
1718
|
+
...launchers.map((name) => `${launcherDestination}/${name}`),
|
|
1719
|
+
...managedDestinations,
|
|
1720
|
+
'package.json',
|
|
1721
|
+
].sort()
|
|
1722
|
+
for (const path of ownedMaterialization) {
|
|
1723
|
+
if (!exactMaterializationPaths.has(path)) {
|
|
1724
|
+
throw new Error(`${label} materialization inventory omits non-provider authority:${path}`)
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
return {
|
|
1729
|
+
discoveryPolicy,
|
|
1730
|
+
launcherDestination,
|
|
1731
|
+
launchers,
|
|
1732
|
+
managedDestinations,
|
|
1733
|
+
managedNamespaceRoots,
|
|
1734
|
+
managedFiles: Object.fromEntries(Object.entries(managedFiles).sort(([left], [right]) => compareUtf8Bytes(left, right))),
|
|
1735
|
+
requiredScriptNames,
|
|
1736
|
+
requiredScripts: Object.fromEntries(Object.entries(requiredScripts)),
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
const assertMonotonicNonProviderAuthority = (previousCorpus, currentCorpus, { exact }) => {
|
|
1741
|
+
const previous = nonProviderTransitionAuthority(previousCorpus, 'previous installed corpus')
|
|
1742
|
+
const current = nonProviderTransitionAuthority(currentCorpus, 'incoming installed corpus')
|
|
1743
|
+
if (current.launcherDestination !== previous.launcherDestination) {
|
|
1744
|
+
throw new Error('ordinary upgrade may not rename the shared launcher destination')
|
|
1745
|
+
}
|
|
1746
|
+
if (exact) {
|
|
1747
|
+
if (JSON.stringify(current) !== JSON.stringify(previous)) {
|
|
1748
|
+
throw new Error('same-version refresh is not an exact non-provider lifecycle replay')
|
|
1749
|
+
}
|
|
1750
|
+
return { previous, current }
|
|
1751
|
+
}
|
|
1752
|
+
for (const [kind, previousNames, currentNames] of [
|
|
1753
|
+
['shared launcher', previous.launchers, current.launchers],
|
|
1754
|
+
['consumer managed-file authority', previous.managedDestinations, current.managedDestinations],
|
|
1755
|
+
['required package script', previous.requiredScriptNames, current.requiredScriptNames],
|
|
1756
|
+
]) {
|
|
1757
|
+
const incoming = new Set(currentNames)
|
|
1758
|
+
for (const name of previousNames) {
|
|
1759
|
+
if (!incoming.has(name)) {
|
|
1760
|
+
throw new Error(`ordinary upgrade may not remove or rename ${kind}; it would leave an orphan:${name}`)
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
for (const name of previous.requiredScriptNames) {
|
|
1765
|
+
if (current.requiredScripts[name] !== previous.requiredScripts[name]) {
|
|
1766
|
+
throw new Error(`ordinary upgrade may not change an existing required package script command; move command evolution through the reviewed bootstrap boundary:${name}`)
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
const previousManagedDestinations = new Set(previous.managedDestinations.map(portableRepositoryPathKey))
|
|
1770
|
+
for (const destination of current.managedDestinations) {
|
|
1771
|
+
const destinationKey = portableRepositoryPathKey(destination)
|
|
1772
|
+
if (previousManagedDestinations.has(destinationKey)) continue
|
|
1773
|
+
const reserved = previous.managedNamespaceRoots.some((root) => {
|
|
1774
|
+
const rootKey = portableRepositoryPathKey(root)
|
|
1775
|
+
return destinationKey.startsWith(`${rootKey}/`)
|
|
1776
|
+
})
|
|
1777
|
+
if (!reserved) {
|
|
1778
|
+
throw new Error(
|
|
1779
|
+
'ordinary upgrade may not establish consumer managed-file authority in a new product namespace; '
|
|
1780
|
+
+ `use the reviewed bootstrap boundary:${destination}`,
|
|
1781
|
+
)
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
for (const key of DISCOVERY_KEYS) {
|
|
1785
|
+
const incoming = new Set(current.discoveryPolicy[key].map(portableRepositoryPathKey))
|
|
1786
|
+
for (const entry of previous.discoveryPolicy[key]) {
|
|
1787
|
+
if (!incoming.has(portableRepositoryPathKey(entry))) {
|
|
1788
|
+
throw new Error(`ordinary upgrade may not shrink or rename discovery policy ${key}:${entry}`)
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
const previousEntries = new Set(previous.discoveryPolicy[key].map(portableRepositoryPathKey))
|
|
1792
|
+
for (const entry of current.discoveryPolicy[key]) {
|
|
1793
|
+
if (
|
|
1794
|
+
!previousEntries.has(portableRepositoryPathKey(entry))
|
|
1795
|
+
&& !discoveryPolicyReservesPath(previous.discoveryPolicy, entry)
|
|
1796
|
+
) {
|
|
1797
|
+
throw new Error(
|
|
1798
|
+
'ordinary upgrade may not establish a new provider discovery namespace; '
|
|
1799
|
+
+ `use the reviewed bootstrap boundary:${key}:${entry}`,
|
|
1800
|
+
)
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
return { previous, current }
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
// Ordinary upgrades authenticate both ends of the transition. Provider surfaces may disappear only
|
|
1808
|
+
// through an exact tombstone. Shared launchers, consumer-managed files, and required package scripts
|
|
1809
|
+
// are monotonic because the generic materializer updates/adds them but intentionally has no unsigned
|
|
1810
|
+
// deletion/rename path; accepting a drop would leave stale executable authority in the consumer.
|
|
1811
|
+
// Historical tombstones are an append-only prefix and same-version repair is an exact replay.
|
|
1812
|
+
export function validateInstalledProviderTransition(previousCorpus, currentCorpus) {
|
|
1813
|
+
const previousLifecycle = previousCorpus?.manifest?.providerLifecycle
|
|
1814
|
+
const currentLifecycle = currentCorpus?.manifest?.providerLifecycle
|
|
1815
|
+
if (!previousLifecycle || !currentLifecycle) throw new Error('provider transition requires two validated installed corpora')
|
|
1816
|
+
for (const [label, corpus, lifecycle] of [
|
|
1817
|
+
['previous', previousCorpus, previousLifecycle],
|
|
1818
|
+
['current', currentCorpus, currentLifecycle],
|
|
1819
|
+
]) {
|
|
1820
|
+
if (lifecycle.schemaVersion !== 4) continue
|
|
1821
|
+
const snapshotView = snapshotActiveSurfaceView(lifecycle.currentSnapshot)
|
|
1822
|
+
const activeView = snapshotActiveSurfaceView({ providers: corpus.activeProviders })
|
|
1823
|
+
if (JSON.stringify(snapshotView) !== JSON.stringify(activeView)) {
|
|
1824
|
+
throw new Error(`${label} corpus active provider ids/surfaces differ from its authenticated currentSnapshot`)
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
const ordering = compareVersion(currentLifecycle.releaseVersion, previousLifecycle.releaseVersion)
|
|
1828
|
+
if (ordering < 0) throw new Error('provider transition may not downgrade')
|
|
1829
|
+
const nonProviderAuthority = assertMonotonicNonProviderAuthority(previousCorpus, currentCorpus, { exact: ordering === 0 })
|
|
1830
|
+
if (ordering === 0) {
|
|
1831
|
+
for (const [label, evidence] of [
|
|
1832
|
+
['previous provider corpus evidence', previousCorpus.evidence],
|
|
1833
|
+
['current provider corpus evidence', currentCorpus.evidence],
|
|
1834
|
+
]) {
|
|
1835
|
+
exactKeys(evidence, [
|
|
1836
|
+
'consumerBomSha256', 'forkCorpusLockSha256', 'manifestSha256',
|
|
1837
|
+
'providerLifecycleSha256', 'releaseVersion',
|
|
1838
|
+
], label)
|
|
1839
|
+
if (
|
|
1840
|
+
!EXACT_SEMVER.test(evidence.releaseVersion || '')
|
|
1841
|
+
|| evidence.releaseVersion !== currentLifecycle.releaseVersion
|
|
1842
|
+
|| ![
|
|
1843
|
+
evidence.consumerBomSha256,
|
|
1844
|
+
evidence.forkCorpusLockSha256,
|
|
1845
|
+
evidence.manifestSha256,
|
|
1846
|
+
evidence.providerLifecycleSha256,
|
|
1847
|
+
].every((digest) => SHA256.test(digest || ''))
|
|
1848
|
+
) throw new Error(`${label} is invalid or release-mismatched`)
|
|
1849
|
+
}
|
|
1850
|
+
if (
|
|
1851
|
+
JSON.stringify(currentCorpus.evidence) !== JSON.stringify(previousCorpus.evidence)
|
|
1852
|
+
||
|
|
1853
|
+
currentLifecycle.currentSnapshotSha256 !== previousLifecycle.currentSnapshotSha256
|
|
1854
|
+
|| currentLifecycle.currentProviderInventorySha256 !== previousLifecycle.currentProviderInventorySha256
|
|
1855
|
+
|| JSON.stringify(currentLifecycle.retiredProviders) !== JSON.stringify(previousLifecycle.retiredProviders)
|
|
1856
|
+
|| JSON.stringify(currentCorpus.activeProviders) !== JSON.stringify(previousCorpus.activeProviders)
|
|
1857
|
+
) throw new Error(`${currentLifecycle.releaseVersion}:same-version provider refresh is not an exact corpus/lifecycle replay`)
|
|
1858
|
+
return certifyProviderTransition({
|
|
1859
|
+
paths: collectProviderMutationPaths(currentCorpus.manifest, currentCorpus.activeProviders),
|
|
1860
|
+
additions: [],
|
|
1861
|
+
managedAdditions: [],
|
|
1862
|
+
retirements: [],
|
|
1863
|
+
mode: 'replay',
|
|
1864
|
+
})
|
|
1865
|
+
}
|
|
1866
|
+
if (currentLifecycle.schemaVersion !== 4) {
|
|
1867
|
+
throw new Error(`${currentLifecycle.releaseVersion}:legacy provider lifecycle v3 may be a prior corpus or exact same-version replay, never a new upgrade target`)
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
const immutableHeadSurfaceView = snapshotActiveSurfaceView(currentLifecycle.immutableHeadSnapshot)
|
|
1871
|
+
const priorActiveSurfaceView = snapshotActiveSurfaceView({ providers: previousCorpus.activeProviders })
|
|
1872
|
+
if (JSON.stringify(immutableHeadSurfaceView) !== JSON.stringify(priorActiveSurfaceView)) {
|
|
1873
|
+
throw new Error(`${currentLifecycle.releaseVersion}:immutableHeadSnapshot provider ids/surfaces do not exactly match the prior active corpus`)
|
|
1874
|
+
}
|
|
1875
|
+
if (
|
|
1876
|
+
previousLifecycle.schemaVersion === 4
|
|
1877
|
+
&& JSON.stringify(currentLifecycle.immutableHeadSnapshot) !== JSON.stringify(previousLifecycle.currentSnapshot)
|
|
1878
|
+
) {
|
|
1879
|
+
throw new Error(`${currentLifecycle.releaseVersion}:immutableHeadSnapshot is not the exact previous authenticated currentSnapshot`)
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
const previousCommon = previousCorpus.manifest?.consumer?.commonInstruction
|
|
1883
|
+
const currentCommon = currentCorpus.manifest?.consumer?.commonInstruction
|
|
1884
|
+
if (
|
|
1885
|
+
!previousCommon
|
|
1886
|
+
|| !currentCommon
|
|
1887
|
+
|| currentCommon.destination !== previousCommon.destination
|
|
1888
|
+
|| currentCommon.materializerId !== previousCommon.materializerId
|
|
1889
|
+
) {
|
|
1890
|
+
throw new Error(`${currentLifecycle.releaseVersion}:ordinary upgrade may not rename or rematerialize the common instruction authority`)
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
if (currentLifecycle.previousSnapshotSha256 !== previousLifecycle.currentSnapshotSha256) {
|
|
1894
|
+
throw new Error(`${currentLifecycle.releaseVersion}:provider transition does not bind immediately previous release ${previousLifecycle.releaseVersion}`)
|
|
1895
|
+
}
|
|
1896
|
+
if (
|
|
1897
|
+
currentLifecycle.immutableHead.releaseVersion !== previousLifecycle.releaseVersion
|
|
1898
|
+
|| currentLifecycle.immutableHead.snapshotSha256 !== previousLifecycle.currentSnapshotSha256
|
|
1899
|
+
|| currentLifecycle.immutableHead.providerInventorySha256 !== previousLifecycle.currentProviderInventorySha256
|
|
1900
|
+
) throw new Error(`${currentLifecycle.releaseVersion}:immutable provider head does not bind previous release inventory`)
|
|
1901
|
+
|
|
1902
|
+
const previousHistory = previousLifecycle.retiredProviders
|
|
1903
|
+
const currentHistory = currentLifecycle.retiredProviders
|
|
1904
|
+
if (currentHistory.length < previousHistory.length || JSON.stringify(currentHistory.slice(0, previousHistory.length)) !== JSON.stringify(previousHistory)) {
|
|
1905
|
+
throw new Error(`${currentLifecycle.releaseVersion}:retired provider history is not append-only`)
|
|
1906
|
+
}
|
|
1907
|
+
const previousById = new Map(currentLifecycle.immutableHeadSnapshot.providers.map((provider) => [provider.id, provider]))
|
|
1908
|
+
const currentById = new Map(currentLifecycle.currentSnapshot.providers.map((provider) => [provider.id, provider]))
|
|
1909
|
+
for (const [id, previous] of previousById) {
|
|
1910
|
+
const current = currentById.get(id)
|
|
1911
|
+
if (!current) continue
|
|
1912
|
+
const currentKinds = new Map(current.surfaces.map((surface) => [surface.path, surface.kind]))
|
|
1913
|
+
for (const surface of previous.surfaces) {
|
|
1914
|
+
if (currentKinds.get(surface.path) !== surface.kind) {
|
|
1915
|
+
throw new Error(`${currentLifecycle.releaseVersion}:${id} removed or changed ${surface.path} without retiring the provider`)
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
const previousSurfacePaths = new Set(previous.surfaces.map((surface) => portableRepositoryPathKey(surface.path)))
|
|
1919
|
+
for (const surface of current.surfaces) {
|
|
1920
|
+
if (
|
|
1921
|
+
!previousSurfacePaths.has(portableRepositoryPathKey(surface.path))
|
|
1922
|
+
&& !discoveryPolicyReservesPath(previous.discovery, surface.path)
|
|
1923
|
+
) {
|
|
1924
|
+
throw new Error(
|
|
1925
|
+
`${currentLifecycle.releaseVersion}:${id} added managed surface outside its immutable provider discovery namespace:`
|
|
1926
|
+
+ `${surface.path}; establish new namespaces through reviewed bootstrap`,
|
|
1927
|
+
)
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
const removedIds = [...previousById.keys()].filter((id) => !currentById.has(id)).sort()
|
|
1932
|
+
const appended = currentHistory.slice(previousHistory.length)
|
|
1933
|
+
const appendedIds = appended.map((retirement) => retirement.id).sort()
|
|
1934
|
+
if (JSON.stringify(removedIds) !== JSON.stringify(appendedIds)) {
|
|
1935
|
+
throw new Error(`${currentLifecycle.releaseVersion}:provider additions/removals do not match appended retirement history`)
|
|
1936
|
+
}
|
|
1937
|
+
const activeSurfaces = currentLifecycle.currentSnapshot.providers.flatMap((provider) => provider.surfaces.map((surface) => ({ ...surface, providerId: provider.id })))
|
|
1938
|
+
for (const retirement of appended) {
|
|
1939
|
+
const previous = previousById.get(retirement.id)
|
|
1940
|
+
const transfers = retirement.transfers || []
|
|
1941
|
+
const partition = [
|
|
1942
|
+
...retirement.surfaces.map(({ path, kind }) => ({ path, kind })),
|
|
1943
|
+
...transfers.map(({ path, kind }) => ({ path, kind })),
|
|
1944
|
+
].sort((left, right) => compareUtf8Bytes(left.path, right.path))
|
|
1945
|
+
if (partition.length !== new Set(partition.map((surface) => portableRepositoryPathKey(surface.path))).size || JSON.stringify(partition) !== JSON.stringify(previous.surfaces)) {
|
|
1946
|
+
throw new Error(`${retirement.id}:deletion and transfer records do not exactly partition previously managed destinations`)
|
|
1947
|
+
}
|
|
1948
|
+
const replacement = retirement.replacementProviderId === null ? null : currentById.get(retirement.replacementProviderId)
|
|
1949
|
+
if (retirement.replacementProviderId !== null && (!replacement || retirement.replacementProviderId === retirement.id)) {
|
|
1950
|
+
throw new Error(`${retirement.id}:replacementProviderId must name a distinct active current provider`)
|
|
1951
|
+
}
|
|
1952
|
+
if (!replacement && transfers.length) throw new Error(`${retirement.id}:null replacementProviderId cannot transfer surfaces`)
|
|
1953
|
+
for (const surface of retirement.surfaces) {
|
|
1954
|
+
if (activeSurfaces.some((candidate) => pathsOverlap(candidate.path, surface.path))) {
|
|
1955
|
+
throw new Error(`${retirement.id}:deletion tombstone remains owned by an active provider:${surface.path}`)
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
for (const surface of transfers) {
|
|
1959
|
+
if (!replacement.surfaces.some((candidate) => candidate.path === surface.path && candidate.kind === surface.kind)) {
|
|
1960
|
+
throw new Error(`${retirement.id}:transfer is not exactly owned by replacement ${retirement.replacementProviderId}:${surface.path}`)
|
|
1961
|
+
}
|
|
1962
|
+
if (activeSurfaces.some((candidate) => candidate.providerId !== replacement.id && pathsOverlap(candidate.path, surface.path))) {
|
|
1963
|
+
throw new Error(`${retirement.id}:transfer is also owned by a non-replacement provider:${surface.path}`)
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
if (
|
|
1967
|
+
retirement.retiredInVersion !== currentLifecycle.releaseVersion
|
|
1968
|
+
|| retirement.priorReleaseVersion !== previousLifecycle.releaseVersion
|
|
1969
|
+
|| retirement.priorSnapshotSha256 !== previousLifecycle.currentSnapshotSha256
|
|
1970
|
+
) throw new Error(`${retirement.id}:retirement version/origin does not bind the current transition`)
|
|
1971
|
+
}
|
|
1972
|
+
const previousActivePaths = new Map(currentLifecycle.immutableHeadSnapshot.providers.flatMap((provider) => (
|
|
1973
|
+
provider.surfaces.map((surface) => [surface.path, surface.kind])
|
|
1974
|
+
)))
|
|
1975
|
+
const additions = currentLifecycle.currentSnapshot.providers.flatMap((provider) => provider.surfaces
|
|
1976
|
+
.filter((surface) => previousActivePaths.get(surface.path) !== surface.kind)
|
|
1977
|
+
.map((surface) => ({ providerId: provider.id, path: surface.path, kind: surface.kind })))
|
|
1978
|
+
.sort((left, right) => (
|
|
1979
|
+
compareUtf8Bytes(left.path, right.path) || compareUtf8Bytes(left.providerId, right.providerId)
|
|
1980
|
+
))
|
|
1981
|
+
for (const addition of additions) {
|
|
1982
|
+
if (!discoveryPolicyReservesPath(nonProviderAuthority.previous.discoveryPolicy, addition.path)) {
|
|
1983
|
+
throw new Error(
|
|
1984
|
+
`${currentLifecycle.releaseVersion}:${addition.providerId} added managed surface outside every previously reviewed provider namespace:`
|
|
1985
|
+
+ `${addition.path}; establish the namespace through reviewed bootstrap`,
|
|
1986
|
+
)
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
const previousManagedDestinations = new Set(
|
|
1990
|
+
nonProviderAuthority.previous.managedDestinations.map(portableRepositoryPathKey),
|
|
1991
|
+
)
|
|
1992
|
+
const managedAdditions = nonProviderAuthority.current.managedDestinations
|
|
1993
|
+
.filter((destination) => !previousManagedDestinations.has(portableRepositoryPathKey(destination)))
|
|
1994
|
+
.sort()
|
|
1995
|
+
return certifyProviderTransition({
|
|
1996
|
+
paths: collectProviderMutationPaths(currentCorpus.manifest, currentCorpus.activeProviders),
|
|
1997
|
+
additions,
|
|
1998
|
+
managedAdditions,
|
|
1999
|
+
retirements: structuredClone(appended),
|
|
2000
|
+
mode: 'upgrade',
|
|
2001
|
+
})
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
export function assertUnclaimedManagedAdditions(projectDir, managedAdditions) {
|
|
2005
|
+
projectDir = canonicalRepositoryRoot(projectDir)
|
|
2006
|
+
if (!Array.isArray(managedAdditions)) throw new Error('managed additions must be a closed array')
|
|
2007
|
+
const normalized = managedAdditions.map((destination, index) => {
|
|
2008
|
+
const path = normalizeRepositoryRelative(destination, `managed transition addition[${index}]`)
|
|
2009
|
+
if (path !== destination) throw new Error(`managed transition addition[${index}] is not canonical`)
|
|
2010
|
+
return path
|
|
2011
|
+
})
|
|
2012
|
+
if (
|
|
2013
|
+
JSON.stringify(normalized) !== JSON.stringify([...new Set(normalized)].sort())
|
|
2014
|
+
|| normalized.length !== new Set(normalized.map(portableRepositoryPathKey)).size
|
|
2015
|
+
) throw new Error('managed additions must be sorted, unique, and portable')
|
|
2016
|
+
for (const destination of normalized) {
|
|
2017
|
+
const collision = existingPortablePathCollision(projectDir, destination)
|
|
2018
|
+
if (collision) {
|
|
2019
|
+
throw new Error(`new consumer managed-file target already exists:${destination}`)
|
|
2020
|
+
}
|
|
2021
|
+
assertNoSymlinkPath(projectDir, join(projectDir, destination), `new consumer managed-file target:${destination}`)
|
|
2022
|
+
}
|
|
2023
|
+
return normalized
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
const refreshTransactionCapabilities = new WeakSet()
|
|
2027
|
+
export function authorizeProviderRefreshTransaction({ verifiedCorpus, providerTransition, journalProviderSnapshot }) {
|
|
2028
|
+
if (!verifiedCorpus || !providerTransition || !journalProviderSnapshot) throw new Error('provider refresh authorization requires validated corpus, transition, and durable journal snapshot')
|
|
2029
|
+
exactKeys(journalProviderSnapshot, ['entries', 'evidence', 'evidenceRoot', 'paths'], 'durable provider journal snapshot')
|
|
2030
|
+
if (
|
|
2031
|
+
!validatedProviderTransitions.has(providerTransition)
|
|
2032
|
+
||
|
|
2033
|
+
JSON.stringify(journalProviderSnapshot.evidence) !== JSON.stringify(verifiedCorpus.evidence)
|
|
2034
|
+
|| typeof journalProviderSnapshot.evidenceRoot !== 'string'
|
|
2035
|
+
|| !isAbsolute(journalProviderSnapshot.evidenceRoot)
|
|
2036
|
+
|| JSON.stringify(journalProviderSnapshot.paths) !== JSON.stringify(providerTransition.paths)
|
|
2037
|
+
|| !Array.isArray(journalProviderSnapshot.entries)
|
|
2038
|
+
|| journalProviderSnapshot.entries.length === 0
|
|
2039
|
+
|| journalProviderSnapshot.entries.length !== providerTransition.paths.length
|
|
2040
|
+
) throw new Error('provider refresh authorization is not bound to the durable journal/corpus path set')
|
|
2041
|
+
const backupPaths = new Set()
|
|
2042
|
+
for (const [index, entry] of journalProviderSnapshot.entries.entries()) {
|
|
2043
|
+
exactKeys(entry, ['backup', 'path', 'present', 'source'], `durable provider journal entry[${index}]`)
|
|
2044
|
+
const expectedPath = providerTransition.paths[index].path
|
|
2045
|
+
const expectedSource = join(verifiedCorpus.projectDir, expectedPath)
|
|
2046
|
+
if (
|
|
2047
|
+
entry.path !== expectedPath
|
|
2048
|
+
|| entry.source !== expectedSource
|
|
2049
|
+
|| typeof entry.backup !== 'string'
|
|
2050
|
+
|| !isAbsolute(entry.backup)
|
|
2051
|
+
|| entry.backup === entry.source
|
|
2052
|
+
|| typeof entry.present !== 'boolean'
|
|
2053
|
+
|| backupPaths.has(portableRepositoryPathKey(entry.backup))
|
|
2054
|
+
) throw new Error(`durable provider journal entry[${index}] is not a closed recovery record`)
|
|
2055
|
+
backupPaths.add(portableRepositoryPathKey(entry.backup))
|
|
2056
|
+
}
|
|
2057
|
+
const capability = Object.freeze({ mode: 'durable', verifiedCorpus, providerTransition, journalProviderSnapshot })
|
|
2058
|
+
refreshTransactionCapabilities.add(capability)
|
|
2059
|
+
return capability
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
// Evidence reconstruction and clean-room tests intentionally mutate only disposable sandboxes and
|
|
2063
|
+
// therefore have no crash-recovery journal to bind. Keep that authority explicit, project-bound,
|
|
2064
|
+
// validated, and one-shot rather than weakening the production durable authorizer with fake entries.
|
|
2065
|
+
export function authorizeDisposableProviderRefreshTransaction({ projectDir, verifiedCorpus, providerTransition }) {
|
|
2066
|
+
const disposableRoot = canonicalRepositoryRoot(projectDir)
|
|
2067
|
+
if (
|
|
2068
|
+
!verifiedCorpus
|
|
2069
|
+
|| disposableRoot !== verifiedCorpus.projectDir
|
|
2070
|
+
|| !validatedProviderTransitions.has(providerTransition)
|
|
2071
|
+
|| JSON.stringify(providerTransition.paths) !== JSON.stringify(collectProviderMutationPaths(verifiedCorpus.manifest))
|
|
2072
|
+
) throw new Error('disposable provider refresh authorization is not bound to its validated sandbox corpus/transition')
|
|
2073
|
+
const capability = Object.freeze({
|
|
2074
|
+
mode: 'disposable',
|
|
2075
|
+
projectDir: disposableRoot,
|
|
2076
|
+
verifiedCorpus,
|
|
2077
|
+
providerTransition,
|
|
2078
|
+
})
|
|
2079
|
+
refreshTransactionCapabilities.add(capability)
|
|
2080
|
+
return capability
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
// 刷新 projectDir 的接線骨架;回傳 {copied, removed, settingsMerged, skills, commands, agents, codex, governance, managed, codexSkipped, skipped, dryRun}
|
|
2084
|
+
// opts.dryRun = true → 只計算會做什麼(result 照填),不寫任何檔(§15 dry-run 契約)
|
|
2085
|
+
export function refreshLaunchers(projectDir, opts = {}) {
|
|
2086
|
+
projectDir = canonicalRepositoryRoot(projectDir)
|
|
2087
|
+
const dryRun = !!opts.dryRun
|
|
2088
|
+
const result = { copied: [], removed: [], retired: [], settingsMerged: false, skills: [], commands: [], agents: [], providers: {}, codex: [], governance: [], managed: [], codexSkipped: [], skipped: null, dryRun }
|
|
2089
|
+
|
|
2090
|
+
// opt-out:fork user 明確不要官方覆蓋骨架
|
|
2091
|
+
if (existsSync(join(projectDir, '.github/no-governance-sync'))) {
|
|
2092
|
+
result.skipped = 'opt-out(.github/no-governance-sync 存在)'
|
|
2093
|
+
return result
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
const candidateForkRoot = join(projectDir, 'node_modules/@qijenchen/design-system/ds-canonical/fork')
|
|
2097
|
+
const src = join(candidateForkRoot, 'launchers')
|
|
2098
|
+
assertSafeTree(projectDir, src, 'installed launcher corpus', { allowMissing: true })
|
|
2099
|
+
if (!existsSync(join(src, 'settings-hooks.json'))) {
|
|
2100
|
+
// 只有「治理 corpus 整個不在」才是合法 skip(舊 npm / 未安裝)。corpus 在、卻沒有本世代的
|
|
2101
|
+
// settings-hooks.json = 另一世代把 merge policy 搬走了 → fail-closed throw,絕不靜默 skip
|
|
2102
|
+
//(F6 對偶:靜默 skip 讓 fork 永遠停在 stale 接線,治理斷線無人知)。
|
|
2103
|
+
if (existsSync(join(candidateForkRoot, 'manifest.json'))) {
|
|
2104
|
+
throw new Error(
|
|
2105
|
+
'installed fork corpus exists but ships no launchers/settings-hooks.json — the installed release uses a scaffold generation this scripts/refresh-fork-launchers.mjs does not understand; '
|
|
2106
|
+
+ 'migrate the fork scaffold first (npm run sync-all -- --apply --to <exact-version>) instead of refreshing with this script generation.'
|
|
2107
|
+
)
|
|
2108
|
+
}
|
|
2109
|
+
result.skipped = 'launchers 未 ship(npm 套件版本過舊或治理本體未安裝)'
|
|
2110
|
+
return result
|
|
2111
|
+
}
|
|
2112
|
+
const verifiedCorpus = opts.verifiedCorpus || validateInstalledForkCorpus(projectDir)
|
|
2113
|
+
const { forkRoot, manifest } = verifiedCorpus
|
|
2114
|
+
const providerTransition = opts.providerTransition || validateInstalledProviderTransition(verifiedCorpus, verifiedCorpus)
|
|
2115
|
+
if (
|
|
2116
|
+
!validatedProviderTransitions.has(providerTransition)
|
|
2117
|
+
|| JSON.stringify(providerTransition.paths) !== JSON.stringify(collectProviderMutationPaths(manifest))
|
|
2118
|
+
) {
|
|
2119
|
+
throw new Error('prepared provider transition does not match the validated installed manifest')
|
|
2120
|
+
}
|
|
2121
|
+
if (
|
|
2122
|
+
!Array.isArray(providerTransition.additions)
|
|
2123
|
+
|| !Array.isArray(providerTransition.managedAdditions)
|
|
2124
|
+
|| !Array.isArray(providerTransition.retirements)
|
|
2125
|
+
) {
|
|
2126
|
+
throw new Error('prepared provider transition has no closed addition/retirement authorization')
|
|
2127
|
+
}
|
|
2128
|
+
if (!dryRun) {
|
|
2129
|
+
const capability = opts.transactionCapability
|
|
2130
|
+
if (
|
|
2131
|
+
!refreshTransactionCapabilities.has(capability)
|
|
2132
|
+
|| capability.verifiedCorpus !== verifiedCorpus
|
|
2133
|
+
|| capability.providerTransition !== providerTransition
|
|
2134
|
+
|| !['durable', 'disposable'].includes(capability.mode)
|
|
2135
|
+
|| (capability.mode === 'disposable' && capability.projectDir !== projectDir)
|
|
2136
|
+
|| (capability.mode === 'durable' && !capability.journalProviderSnapshot)
|
|
2137
|
+
) throw new Error('provider refresh mutation requires an authorized one-shot transaction capability')
|
|
2138
|
+
// One capability authorizes one materialization attempt. Rollback remains sync-all's job.
|
|
2139
|
+
refreshTransactionCapabilities.delete(capability)
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
// Parse and close-shape the BOM-bound settings migration policy before the first workspace
|
|
2143
|
+
// mutation. A malformed generated policy can therefore never leave a partial launcher refresh.
|
|
2144
|
+
const canonical = validateCanonicalSettingsWiring(JSON.parse(readFileSync(join(src, 'settings-hooks.json'), 'utf8')), manifest)
|
|
2145
|
+
const packageManifestPath = regularUnaliasedFile(projectDir, join(projectDir, 'package.json'), 'consumer package manifest')
|
|
2146
|
+
const packageManifestInfo = lstatSync(packageManifestPath)
|
|
2147
|
+
const currentPackageManifest = JSON.parse(readFileSync(packageManifestPath, 'utf8'))
|
|
2148
|
+
const requiredPackageManifest = mergeRequiredPackageScripts(currentPackageManifest, verifiedCorpus.bom.payload.requiredScripts)
|
|
2149
|
+
const packageScriptsChanged = JSON.stringify(currentPackageManifest.scripts || {}) !== JSON.stringify(requiredPackageManifest.scripts)
|
|
2150
|
+
|
|
2151
|
+
// A generic materializer is intentionally future-compatible, but that cannot become authority
|
|
2152
|
+
// to adopt an existing product file merely because the candidate added a destination under an
|
|
2153
|
+
// already reserved namespace. Every newly claimed managed file must still be absent (including
|
|
2154
|
+
// portable case/Unicode aliases) before the first mutation.
|
|
2155
|
+
assertUnclaimedManagedAdditions(projectDir, providerTransition.managedAdditions)
|
|
2156
|
+
|
|
2157
|
+
// A newly claimed provider root has no prior consumer ownership proof. An authenticated release
|
|
2158
|
+
// may declare the new surface, but it may not silently adopt or clobber a pre-existing product
|
|
2159
|
+
// file/tree at that destination. Retained and explicitly transferred surfaces remain governed.
|
|
2160
|
+
for (const [index, addition] of providerTransition.additions.entries()) {
|
|
2161
|
+
exactKeys(addition, ['kind', 'path', 'providerId'], `provider transition addition[${index}]`)
|
|
2162
|
+
validateSurface({ path: addition.path, kind: addition.kind }, `provider transition addition[${index}]`)
|
|
2163
|
+
if (!/^[a-z][a-z0-9-]*$/.test(addition.providerId || '')) throw new Error(`provider transition addition[${index}].providerId is invalid`)
|
|
2164
|
+
const target = join(projectDir, addition.path)
|
|
2165
|
+
const portableCollision = existingPortablePathCollision(projectDir, addition.path)
|
|
2166
|
+
if (portableCollision) {
|
|
2167
|
+
throw new Error(`new provider surface target already exists:${addition.providerId}:${addition.path}`)
|
|
2168
|
+
}
|
|
2169
|
+
assertNoSymlinkPath(projectDir, target, `new provider surface target:${addition.providerId}:${addition.path}`)
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
// Resolve and validate every closed-tree source/target before the first provider mutation. The
|
|
2173
|
+
// installed manifest and corpus lock authorize the mapping; filesystem aliases may not redirect
|
|
2174
|
+
// either side outside its authenticated root.
|
|
2175
|
+
const productTreePlans = new Map()
|
|
2176
|
+
for (const [providerId, surface] of Object.entries(manifest.providerSurfaces).sort(([a], [b]) => compareUtf8Bytes(a, b))) {
|
|
2177
|
+
if (!surface.generated) continue
|
|
2178
|
+
for (const tree of surface.trees) {
|
|
2179
|
+
if (!safeRelative(forkRoot, tree.artifactRoot) || !safeRelative(projectDir, tree.destination)) {
|
|
2180
|
+
throw new Error(`unsafe ${providerId} product managed tree mapping:${tree.destination} <- ${tree.artifactRoot}`)
|
|
2181
|
+
}
|
|
2182
|
+
const source = join(forkRoot, tree.artifactRoot)
|
|
2183
|
+
const target = join(projectDir, tree.destination)
|
|
2184
|
+
assertSafeTree(projectDir, source, `${providerId} product managed tree ${tree.name} source`)
|
|
2185
|
+
if (!lstatSync(source).isDirectory()) throw new Error(`${providerId} product managed tree source is not a directory:${tree.name}`)
|
|
2186
|
+
assertNoSymlinkPath(projectDir, target, `${providerId} product managed tree ${tree.name} target`)
|
|
2187
|
+
if (pathEntryExists(target)) assertSafeTree(projectDir, target, `${providerId} product managed tree ${tree.name} target`)
|
|
2188
|
+
productTreePlans.set(`${providerId}/${tree.name}`, { source, target, destination: tree.destination })
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
// A provider rename/removal is an authenticated migration, not an inference from missing current
|
|
2193
|
+
// records. Only exact versioned tombstones in the installed corpus may delete old destinations.
|
|
2194
|
+
const retirementPlan = []
|
|
2195
|
+
for (const retired of providerTransition.retirements) {
|
|
2196
|
+
for (const surface of retired.surfaces) {
|
|
2197
|
+
const validated = validateRetiredSurface(surface, `${retired.id} authenticated tombstone`)
|
|
2198
|
+
if (pathsOverlap(validated.path, manifest.consumer.commonInstruction.destination)) {
|
|
2199
|
+
throw new Error(`${retired.id}:provider tombstone may not delete the common instruction authority`)
|
|
2200
|
+
}
|
|
2201
|
+
if (!safeRelative(projectDir, validated.path)) throw new Error(`unsafe retired provider tombstone:${retired.id}:${validated.path}`)
|
|
2202
|
+
const target = join(projectDir, validated.path)
|
|
2203
|
+
if (!pathEntryExists(target)) continue
|
|
2204
|
+
assertNoSymlinkPath(projectDir, target, `retired provider tombstone ${validated.path}`, { allowMissing: false })
|
|
2205
|
+
const info = lstatSync(target)
|
|
2206
|
+
if ((validated.kind === 'file' && !info.isFile()) || (validated.kind === 'tree' && !info.isDirectory())) {
|
|
2207
|
+
throw new Error(`retired provider tombstone kind mismatch:${validated.path}:${validated.kind}`)
|
|
2208
|
+
}
|
|
2209
|
+
if (validated.kind === 'tree') assertSafeTree(projectDir, target, `retired provider tombstone ${validated.path}`)
|
|
2210
|
+
const actualDigest = providerSurfaceDigest(projectDir, validated)
|
|
2211
|
+
if (actualDigest !== validated.sha256) {
|
|
2212
|
+
throw new Error(`retired provider tombstone content differs from immutable prior artifact:${retired.id}:${validated.path}`)
|
|
2213
|
+
}
|
|
2214
|
+
retirementPlan.push({ ...validated, target })
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
// Validate the complete authenticated deletion plan before deleting its first path. A malformed
|
|
2218
|
+
// later record can therefore never leave an earlier provider half-retired.
|
|
2219
|
+
if (!dryRun && packageScriptsChanged) {
|
|
2220
|
+
atomicReplaceRepositoryFile(projectDir, packageManifestPath, JSON.stringify(requiredPackageManifest, null, 2) + '\n', {
|
|
2221
|
+
mode: packageManifestInfo.mode & 0o777,
|
|
2222
|
+
label: 'consumer package governance scripts',
|
|
2223
|
+
})
|
|
2224
|
+
}
|
|
2225
|
+
for (const surface of retirementPlan) {
|
|
2226
|
+
if (!dryRun) rmSync(surface.target, { recursive: surface.kind === 'tree', force: false })
|
|
2227
|
+
result.retired.push(surface.path)
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
// 1. Materialize one provider-neutral launcher tree. Provider configs may reference it, but no
|
|
2231
|
+
// provider owns it; retiring `.claude` therefore cannot delete Codex/future runtime dependencies.
|
|
2232
|
+
const launcherDestination = normalizeRepositoryRelative(manifest.consumer.launcherDestination, 'shared launcher destination')
|
|
2233
|
+
const launcherDir = join(projectDir, launcherDestination)
|
|
2234
|
+
assertNoSymlinkPath(projectDir, launcherDir, 'shared launcher directory')
|
|
2235
|
+
if (!dryRun) {
|
|
2236
|
+
mkdirSync(launcherDir, { recursive: true })
|
|
2237
|
+
assertNoSymlinkPath(projectDir, launcherDir, 'shared launcher directory', { allowMissing: false })
|
|
2238
|
+
}
|
|
2239
|
+
const launcherFiles = readdirSync(src).filter((f) => f.endsWith('.sh') || f.endsWith('.mjs') || f.endsWith('.py')).sort()
|
|
2240
|
+
if (JSON.stringify(launcherFiles) !== JSON.stringify(manifest.launchers)) {
|
|
2241
|
+
throw new Error('installed launcher artifact tree differs from its closed manifest inventory')
|
|
2242
|
+
}
|
|
2243
|
+
for (const f of launcherFiles) {
|
|
2244
|
+
if (!dryRun) safeCopyFile({ sourceRoot: projectDir, source: join(src, f), targetRoot: projectDir, target: join(launcherDir, f), label: `launcher ${f}` })
|
|
2245
|
+
result.copied.push(f)
|
|
2246
|
+
}
|
|
2247
|
+
// 2. Idempotently merge every registered template-backed hook surface. The policy inventory,
|
|
2248
|
+
// materializer and base-template plugin are all manifest/corpus facts; provider ids never select
|
|
2249
|
+
// implementation behavior.
|
|
2250
|
+
for (const [mergeProviderId, policy] of Object.entries(canonical.surfaces).sort(([a], [b]) => compareUtf8Bytes(a, b))) {
|
|
2251
|
+
const settingsRelative = normalizeRepositoryRelative(policy.hookDestination, `${mergeProviderId} template-backed hook destination`)
|
|
2252
|
+
const settingsPath = join(projectDir, settingsRelative)
|
|
2253
|
+
const canonicalHookPath = join(forkRoot, policy.hookArtifact)
|
|
2254
|
+
assertNoSymlinkPath(projectDir, settingsPath, settingsRelative)
|
|
2255
|
+
assertNoSymlinkPath(projectDir, canonicalHookPath, `${mergeProviderId} canonical hook artifact`, { allowMissing: false })
|
|
2256
|
+
const canonicalConfig = JSON.parse(readFileSync(canonicalHookPath, 'utf8'))
|
|
2257
|
+
const currentSettings = existsSync(settingsPath) ? parseJsonc(readFileSync(settingsPath, 'utf8'), settingsRelative) : {}
|
|
2258
|
+
const merged = mergeProviderHookSettings({
|
|
2259
|
+
settings: currentSettings,
|
|
2260
|
+
canonicalConfig,
|
|
2261
|
+
policy,
|
|
2262
|
+
label: `${mergeProviderId} ${settingsRelative}`,
|
|
2263
|
+
})
|
|
2264
|
+
for (const retiredId of merged.retiredIds) if (!result.removed.includes(retiredId)) result.removed.push(retiredId)
|
|
2265
|
+
if (!dryRun) {
|
|
2266
|
+
atomicReplaceRepositoryFile(projectDir, settingsPath, JSON.stringify(merged.settings, null, 2) + '\n', {
|
|
2267
|
+
mode: 0o600,
|
|
2268
|
+
label: settingsRelative,
|
|
2269
|
+
})
|
|
2270
|
+
}
|
|
2271
|
+
result.settingsMerged = true
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
// 3. Materialize only registry-declared legacy discovery leaves. Their exact destinations are
|
|
2275
|
+
// provider lifecycle surfaces, so retiring the owning provider removes them and this phase cannot
|
|
2276
|
+
// resurrect a provider-specific commands/agents tree.
|
|
2277
|
+
for (const category of manifest.consumer.compatibilityCategories) {
|
|
2278
|
+
for (const name of category.entries) {
|
|
2279
|
+
const source = join(forkRoot, category.artifactRoot, name)
|
|
2280
|
+
const destination = join(projectDir, category.destinationRoot, name)
|
|
2281
|
+
if (!dryRun) safeCopyFile({ sourceRoot: projectDir, source, targetRoot: projectDir, target: destination, label: `${category.name}/${name}` })
|
|
2282
|
+
result[category.name].push(name)
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
// Materialize every BOM-enforced body from the exact installed corpus. This is deliberately
|
|
2287
|
+
// generic: a future release may add a managed file without requiring an older consumer's
|
|
2288
|
+
// sync-all implementation to know that filename. Product-owned files are never in this map.
|
|
2289
|
+
for (const [destination, artifact] of Object.entries(manifest?.consumer?.managedFiles || {}).sort(([a], [b]) => compareUtf8Bytes(a, b))) {
|
|
2290
|
+
if (!safeRelative(projectDir, destination) || !safeRelative(forkRoot, artifact)) {
|
|
2291
|
+
throw new Error(`unsafe consumer managed-file mapping:${destination} <- ${artifact}`)
|
|
2292
|
+
}
|
|
2293
|
+
const source = join(forkRoot, artifact)
|
|
2294
|
+
const target = join(projectDir, destination)
|
|
2295
|
+
if (!dryRun) safeCopyFile({
|
|
2296
|
+
sourceRoot: projectDir,
|
|
2297
|
+
source,
|
|
2298
|
+
targetRoot: projectDir,
|
|
2299
|
+
target,
|
|
2300
|
+
label: `consumer managed body ${destination}`,
|
|
2301
|
+
})
|
|
2302
|
+
result.managed.push(destination)
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
// The shared instruction is materialized once from its consumer-owned artifact. A provider using
|
|
2306
|
+
// shared-instruction-v1 only references this destination; provider refresh and retirement never
|
|
2307
|
+
// own, duplicate, or delete it.
|
|
2308
|
+
const commonInstruction = manifest.consumer.commonInstruction
|
|
2309
|
+
const commonSource = join(forkRoot, commonInstruction.artifact)
|
|
2310
|
+
const commonDestination = join(projectDir, commonInstruction.destination)
|
|
2311
|
+
if (!dryRun) safeCopyFile({
|
|
2312
|
+
sourceRoot: projectDir,
|
|
2313
|
+
source: commonSource,
|
|
2314
|
+
targetRoot: projectDir,
|
|
2315
|
+
target: commonDestination,
|
|
2316
|
+
label: 'common instruction authority',
|
|
2317
|
+
})
|
|
2318
|
+
result.governance.push(commonInstruction.destination)
|
|
2319
|
+
|
|
2320
|
+
// 4. Provider surfaces—the installed manifest is the inventory. New providers materialize
|
|
2321
|
+
// without adding another provider-id branch here. The template-backed hook view is merged above so
|
|
2322
|
+
// consumer-owned non-governance hooks survive; all other declared hook views are exact generated
|
|
2323
|
+
// files. Skills clobber only the governance-owned names listed by the manifest.
|
|
2324
|
+
if (manifest?.providerSurfaces) {
|
|
2325
|
+
for (const [providerId, surface] of Object.entries(manifest.providerSurfaces).sort(([a], [b]) => compareUtf8Bytes(a, b))) {
|
|
2326
|
+
result.providers[providerId] = { installed: [], exclusions: surface.exclusions || [] }
|
|
2327
|
+
if (!surface.generated) continue
|
|
2328
|
+
const hookMaterializer = surface.hookMaterializerId
|
|
2329
|
+
? resolveManifestMaterializer(manifest, 'hookViews', surface.hookMaterializerId, `${providerId}.hookMaterializerId`)
|
|
2330
|
+
: null
|
|
2331
|
+
const instructionMaterializer = resolveManifestMaterializer(
|
|
2332
|
+
manifest,
|
|
2333
|
+
'instructionViews',
|
|
2334
|
+
surface.instructionMaterializerId,
|
|
2335
|
+
`${providerId}.instructionMaterializerId`,
|
|
2336
|
+
)
|
|
2337
|
+
const mappings = [
|
|
2338
|
+
...(instructionMaterializer.engine === 'shared-instruction-v1'
|
|
2339
|
+
? []
|
|
2340
|
+
: [['instruction', surface.instructionArtifact, surface.instructionDestination]]),
|
|
2341
|
+
...(surface.hookArtifact && surface.hookDestination && hookMaterializer?.mergeStrategy === 'replace-document-v1'
|
|
2342
|
+
? [['hook', surface.hookArtifact, surface.hookDestination]]
|
|
2343
|
+
: []),
|
|
2344
|
+
]
|
|
2345
|
+
for (const [kind, sourceRel, destination] of mappings) {
|
|
2346
|
+
if (!safeRelative(forkRoot, sourceRel) || !safeRelative(projectDir, destination)) throw new Error(`unsafe ${providerId} ${kind} mapping:${destination} <- ${sourceRel}`)
|
|
2347
|
+
const source = join(forkRoot, sourceRel)
|
|
2348
|
+
if (!existsSync(source)) throw new Error(`${providerId} ${kind} artifact missing:${sourceRel}`)
|
|
2349
|
+
if (!dryRun) safeCopyFile({ sourceRoot: projectDir, source, targetRoot: projectDir, target: join(projectDir, destination), label: `${providerId} ${kind} adapter` })
|
|
2350
|
+
result.providers[providerId].installed.push(destination)
|
|
2351
|
+
if (providerId === 'codex') result.codex.push(destination)
|
|
2352
|
+
}
|
|
2353
|
+
if (hookMaterializer?.mergeStrategy === 'merge-hook-map-into-base-v1') result.providers[providerId].installed.push(surface.hookDestination)
|
|
2354
|
+
for (const name of surface.skills || []) {
|
|
2355
|
+
const sourceRel = `${surface.skillArtifactRoot}/${name}`
|
|
2356
|
+
const destination = `${surface.skillDestination}/${name}`
|
|
2357
|
+
if (!safeRelative(forkRoot, sourceRel) || !safeRelative(projectDir, destination)) throw new Error(`unsafe ${providerId} skill mapping:${destination} <- ${sourceRel}`)
|
|
2358
|
+
const source = join(forkRoot, sourceRel)
|
|
2359
|
+
if (!existsSync(source)) throw new Error(`${providerId} skill artifact missing:${sourceRel}`)
|
|
2360
|
+
if (!dryRun) safeReplaceTree({ sourceRoot: projectDir, source, targetRoot: projectDir, target: join(projectDir, destination), label: `${providerId} skill ${name}` })
|
|
2361
|
+
result.providers[providerId].installed.push(destination)
|
|
2362
|
+
if (!result.skills.includes(name)) result.skills.push(name)
|
|
2363
|
+
if (providerId === 'codex') result.codex.push(destination)
|
|
2364
|
+
}
|
|
2365
|
+
for (const tree of surface.trees) {
|
|
2366
|
+
const plan = productTreePlans.get(`${providerId}/${tree.name}`)
|
|
2367
|
+
if (!plan) throw new Error(`${providerId} product managed tree has no validated materialization plan:${tree.name}`)
|
|
2368
|
+
if (!dryRun) safeReplaceTree({
|
|
2369
|
+
sourceRoot: projectDir,
|
|
2370
|
+
source: plan.source,
|
|
2371
|
+
targetRoot: projectDir,
|
|
2372
|
+
target: plan.target,
|
|
2373
|
+
label: `${providerId} product managed tree ${tree.name}`,
|
|
2374
|
+
})
|
|
2375
|
+
result.providers[providerId].installed.push(plan.destination)
|
|
2376
|
+
if (providerId === 'codex') result.codex.push(plan.destination)
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
} else if (manifest?.codex) {
|
|
2380
|
+
// Immutable older releases did not yet contain providerSurfaces. Keep a bounded compatibility
|
|
2381
|
+
// reader so exact upgrades from them can reach the first provider-neutral release.
|
|
2382
|
+
const codexMeta = manifest.codex
|
|
2383
|
+
if (codexMeta.agentsMd && codexMeta.instructionDestination && existsSync(join(forkRoot, codexMeta.agentsMd))) {
|
|
2384
|
+
if (!dryRun) safeCopyFile({ sourceRoot: projectDir, source: join(forkRoot, codexMeta.agentsMd), targetRoot: projectDir, target: join(projectDir, codexMeta.instructionDestination), label: 'legacy common instruction adapter' })
|
|
2385
|
+
result.codex.push(codexMeta.instructionDestination)
|
|
2386
|
+
}
|
|
2387
|
+
if ((codexMeta.hooksJson || []).length && codexMeta.hookArtifact && codexMeta.hookDestination && existsSync(join(forkRoot, codexMeta.hookArtifact))) {
|
|
2388
|
+
if (!dryRun) safeCopyFile({ sourceRoot: projectDir, source: join(forkRoot, codexMeta.hookArtifact), targetRoot: projectDir, target: join(projectDir, codexMeta.hookDestination), label: 'legacy provider hook adapter' })
|
|
2389
|
+
result.codex.push(codexMeta.hookDestination)
|
|
2390
|
+
}
|
|
2391
|
+
for (const name of codexMeta.agentsSkills || []) {
|
|
2392
|
+
const source = join(forkRoot, codexMeta.skillArtifactRoot, name)
|
|
2393
|
+
if (!existsSync(source)) continue
|
|
2394
|
+
const destination = `${codexMeta.skillDestination}/${name}`
|
|
2395
|
+
if (!dryRun) safeReplaceTree({ sourceRoot: projectDir, source, targetRoot: projectDir, target: join(projectDir, destination), label: `legacy provider skill ${name}` })
|
|
2396
|
+
result.codex.push(destination)
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
// 5. Immutable consumer BOM/schema. These two paths are governance-owned generated views;
|
|
2401
|
+
// local product policy belongs in governance/overlay.md and is never touched here.
|
|
2402
|
+
if (manifest?.consumer) {
|
|
2403
|
+
for (const [sourceKey, destination] of [['governanceLock', 'lock.json'], ['governanceLockSchema', 'lock.schema.json']]) {
|
|
2404
|
+
const sourceRel = manifest.consumer[sourceKey]
|
|
2405
|
+
if (!sourceRel || !existsSync(join(forkRoot, sourceRel))) continue
|
|
2406
|
+
if (!dryRun) {
|
|
2407
|
+
safeCopyFile({ sourceRoot: projectDir, source: join(forkRoot, sourceRel), targetRoot: projectDir, target: join(projectDir, 'governance', destination), label: `governance/${destination}` })
|
|
2408
|
+
}
|
|
2409
|
+
result.governance.push(`governance/${destination}`)
|
|
2410
|
+
}
|
|
2411
|
+
const checkerSource = manifest.consumer.governanceCheck
|
|
2412
|
+
if (checkerSource && existsSync(join(forkRoot, checkerSource))) {
|
|
2413
|
+
const checkerDestination = join(projectDir, 'scripts/governance-check.mjs')
|
|
2414
|
+
if (!dryRun) {
|
|
2415
|
+
safeCopyFile({
|
|
2416
|
+
sourceRoot: projectDir,
|
|
2417
|
+
source: join(forkRoot, checkerSource),
|
|
2418
|
+
targetRoot: projectDir,
|
|
2419
|
+
target: checkerDestination,
|
|
2420
|
+
label: 'governance checker',
|
|
2421
|
+
mode: 0o755,
|
|
2422
|
+
})
|
|
2423
|
+
}
|
|
2424
|
+
result.governance.push('scripts/governance-check.mjs')
|
|
2425
|
+
}
|
|
2426
|
+
// providerSurfaces installs every instruction. Only older immutable corpora need the legacy
|
|
2427
|
+
// consumer.claudeMd fallback.
|
|
2428
|
+
if (!manifest.providerSurfaces) {
|
|
2429
|
+
const claudeSource = manifest.consumer.claudeMd
|
|
2430
|
+
if (claudeSource && existsSync(join(forkRoot, claudeSource))) {
|
|
2431
|
+
if (!dryRun) safeCopyFile({ sourceRoot: projectDir, source: join(forkRoot, claudeSource), targetRoot: projectDir, target: join(projectDir, claudeSource), label: 'legacy provider instruction adapter' })
|
|
2432
|
+
result.governance.push(claudeSource)
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
result.skills.sort()
|
|
2437
|
+
return result
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
// 允許直接 `node scripts/refresh-fork-launchers.mjs [--dry-run] [--json]`(inject hook self-heal 用);
|
|
2441
|
+
// 被 import(sync-all)時不執行。--dry-run 只算不寫;--json 輸出 machine-readable 結果(§15 契約)。
|
|
2442
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
2443
|
+
const apply = process.argv.includes('--apply')
|
|
2444
|
+
if (apply) {
|
|
2445
|
+
console.error('refresh-fork-launchers: --apply requires the durable sync-all transaction; run sync-all --apply --to <exact-version>')
|
|
2446
|
+
process.exit(2)
|
|
2447
|
+
}
|
|
2448
|
+
const dryRun = true
|
|
2449
|
+
const asJson = process.argv.includes('--json')
|
|
2450
|
+
const r = refreshLaunchers(process.cwd(), { dryRun })
|
|
2451
|
+
if (asJson) console.log(JSON.stringify(r))
|
|
2452
|
+
else if (r.skipped) console.log(`refresh-fork-launchers: skip(${r.skipped})`)
|
|
2453
|
+
else console.log(`refresh-fork-launchers${dryRun ? '(dry-run)' : ''}: launchers ${r.copied.length} / skills ${(r.skills || []).length} / codex ${(r.codex || []).length} / settings ${r.settingsMerged ? 'merged' : '-'}`)
|
|
2454
|
+
}
|