@qijenchen/design-system 0.1.0-beta.13 → 0.1.0-beta.131
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 +75 -0
- package/dist/components/AgentPanel/agent-logo.d.ts.map +1 -0
- package/dist/components/AgentPanel/agent-logo.js +371 -0
- package/dist/components/AgentPanel/agent-logo.js.map +1 -0
- package/dist/components/AgentPanel/agent-panel.d.ts +183 -0
- package/dist/components/AgentPanel/agent-panel.d.ts.map +1 -0
- package/dist/components/AgentPanel/agent-panel.js +900 -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 +16 -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} +59 -0
- package/dist/index.d.ts +121 -67
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +143 -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 +1321 -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 +1321 -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 +672 -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 +1791 -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 +126 -0
- package/src/components/AgentPanel/agent-logo.tsx +491 -0
- package/src/components/AgentPanel/agent-panel.anatomy.stories.tsx +127 -0
- package/src/components/AgentPanel/agent-panel.css +59 -0
- package/src/components/AgentPanel/agent-panel.principles.stories.tsx +57 -0
- package/src/components/AgentPanel/agent-panel.spec.md +319 -0
- package/src/components/AgentPanel/agent-panel.stories.tsx +360 -0
- package/src/components/AgentPanel/agent-panel.tsx +1241 -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 +651 -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 +60 -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,2639 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Explicit, exact consumer upgrade entrypoint.
|
|
3
|
+
//
|
|
4
|
+
// Default behaviour is read-only planning. Workspace mutation requires BOTH an exact
|
|
5
|
+
// version and `--apply`; mutable dist-tags (`beta`, `latest`) and semver ranges are
|
|
6
|
+
// rejected. Normal fleet upgrades should be performed on a bot/user branch and reviewed
|
|
7
|
+
// as a PR. SessionStart must never call this command with --apply.
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
closeSync,
|
|
11
|
+
constants as fsConstants,
|
|
12
|
+
cpSync,
|
|
13
|
+
existsSync,
|
|
14
|
+
fstatSync,
|
|
15
|
+
fsyncSync,
|
|
16
|
+
linkSync,
|
|
17
|
+
lstatSync,
|
|
18
|
+
mkdirSync,
|
|
19
|
+
openSync,
|
|
20
|
+
readFileSync,
|
|
21
|
+
readlinkSync,
|
|
22
|
+
readdirSync,
|
|
23
|
+
realpathSync,
|
|
24
|
+
renameSync,
|
|
25
|
+
rmSync,
|
|
26
|
+
writeFileSync,
|
|
27
|
+
} from 'node:fs'
|
|
28
|
+
import { createHash, randomUUID } from 'node:crypto'
|
|
29
|
+
import { spawnSync } from 'node:child_process'
|
|
30
|
+
import { hostname } from 'node:os'
|
|
31
|
+
import { join, dirname, resolve } from 'node:path'
|
|
32
|
+
import { pathToFileURL } from 'node:url'
|
|
33
|
+
import {
|
|
34
|
+
assertNoSymlinkPath,
|
|
35
|
+
assertSafeTree,
|
|
36
|
+
canonicalRepositoryRoot,
|
|
37
|
+
collectProviderMutationPaths,
|
|
38
|
+
normalizeRepositoryRelative,
|
|
39
|
+
pathEntryExists,
|
|
40
|
+
validateInstalledForkCorpus,
|
|
41
|
+
} from './refresh-fork-launchers.mjs'
|
|
42
|
+
import { validateUpgradeTrustPolicy } from './verify-upgrade-provenance.mjs'
|
|
43
|
+
import { reconstructExpectedUpgrade } from './verify-upgrade-evidence.mjs'
|
|
44
|
+
import { REVIEWED_CONTROL_PLANE_UPDATE_ROUTE } from './lib/consumer-control-plane-policy.mjs'
|
|
45
|
+
import { assertNoRootNpmShrinkwrap } from './lib/governance-dependency-bootstrap.mjs'
|
|
46
|
+
import {
|
|
47
|
+
assertClosedGitLocalConfiguration,
|
|
48
|
+
runClosedGit,
|
|
49
|
+
} from './lib/closed-tool-execution.mjs'
|
|
50
|
+
import { compareUtf8Bytes } from './lib/provider-lifecycle.mjs'
|
|
51
|
+
|
|
52
|
+
const repoRoot = canonicalRepositoryRoot(process.cwd())
|
|
53
|
+
assertNoRootNpmShrinkwrap(repoRoot, { errorPrefix: 'GOV-UPGRADE-LOCK-001' })
|
|
54
|
+
const journalPath = join(repoRoot, '.governance-upgrade-journal.json')
|
|
55
|
+
const OWNER_MARKER = '.governance-upgrade-owner.json'
|
|
56
|
+
const LEGACY_JOURNAL_PUBLISH = '.governance-upgrade-journal.publish'
|
|
57
|
+
const JOURNAL_LEASE_SOURCE = '.governance-upgrade-journal.lease'
|
|
58
|
+
const JOURNAL_LEASE_KIND = 'governance-upgrade-journal-lease'
|
|
59
|
+
const JOURNAL_STATE_DIRECTORY = '.governance-upgrade-journal-states'
|
|
60
|
+
const JOURNAL_STATE_KIND = 'governance-upgrade-journal-state'
|
|
61
|
+
const JOURNAL_STATE_NAME = /^state-([0-9]{16})\.json$/
|
|
62
|
+
const JOURNAL_STATE_PUBLISH_NAME = /^\.state-([0-9]{16})\.publish$/
|
|
63
|
+
const TEST_RELEASE_HANDSHAKE_OPTION = '--test-journal-release-handshake'
|
|
64
|
+
const TEST_RELEASE_ACK = '.governance-upgrade-test-release-ack'
|
|
65
|
+
const TEST_GC_FAILURE_OPTION = '--test-transaction-gc-failure'
|
|
66
|
+
const TEST_NODE_MODULES_PUBLISH_HANDSHAKE_OPTION = '--test-node-modules-publish-handshake'
|
|
67
|
+
const TEST_NODE_MODULES_PUBLISH_ACK = '.governance-upgrade-test-node-modules-publish-ack'
|
|
68
|
+
const TEST_CONSUMER_ROLE_GAP_HANDSHAKE_OPTION = '--test-consumer-role-gap-handshake'
|
|
69
|
+
const TEST_CONSUMER_ROLE_GAP_ACK = '.governance-upgrade-test-consumer-role-gap-ack'
|
|
70
|
+
const PROVIDER_CLI_AUTHORITY_MANIFEST = 'infra/governance/providers/provider-cli-toolchain.json'
|
|
71
|
+
const PROVIDER_CLI_CONSUMER_MANIFEST = 'governance/provider-cli-toolchain.json'
|
|
72
|
+
const authorityProviderManifestPresent = pathEntryExists(join(repoRoot, PROVIDER_CLI_AUTHORITY_MANIFEST))
|
|
73
|
+
const TRANSACTION_PATHS = Object.freeze([
|
|
74
|
+
'package.json', 'package-lock.json', 'npm-shrinkwrap.json', '.npmrc',
|
|
75
|
+
'governance', 'schemas', '.devcontainer', '.github', 'scripts',
|
|
76
|
+
])
|
|
77
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
|
|
78
|
+
const SHA256_RE = /^[a-f0-9]{64}$/
|
|
79
|
+
const sha256 = (value) => createHash('sha256').update(value).digest('hex')
|
|
80
|
+
const ignoredRepositoryPaths = (repository, paths, label) => {
|
|
81
|
+
const normalized = [...new Set(paths.map((path, index) => (
|
|
82
|
+
normalizeRepositoryRelative(path, `${label} path[${index}]`)
|
|
83
|
+
)))].sort()
|
|
84
|
+
if (!normalized.length) return []
|
|
85
|
+
const probe = runClosedGit([
|
|
86
|
+
'ls-files', '--others', '--ignored', '--exclude-standard', '-z', '--', ...normalized,
|
|
87
|
+
], { cwd: repository, output: 'buffer', maxOutputBytes: 64 * 1024 * 1024 })
|
|
88
|
+
if (probe.error || probe.signal !== null || probe.status !== 0 || !Buffer.isBuffer(probe.stdout)) {
|
|
89
|
+
throw new Error(`${label} ignored-inventory probe failed`)
|
|
90
|
+
}
|
|
91
|
+
return Buffer.from(probe.stdout)
|
|
92
|
+
.toString('utf8')
|
|
93
|
+
.split('\0')
|
|
94
|
+
.filter(Boolean)
|
|
95
|
+
.sort()
|
|
96
|
+
}
|
|
97
|
+
const assertNoIgnoredManagedContent = (repository, paths, label) => {
|
|
98
|
+
const ignored = ignoredRepositoryPaths(repository, paths, label)
|
|
99
|
+
if (ignored.length) {
|
|
100
|
+
throw new Error(`${label} contains ignored content outside the reviewable patch boundary:${ignored.join(',')}`)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const processIsAlive = (pid) => {
|
|
104
|
+
try {
|
|
105
|
+
process.kill(pid, 0)
|
|
106
|
+
return true
|
|
107
|
+
} catch (error) {
|
|
108
|
+
return error?.code !== 'ESRCH'
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const exactKeys = (value, keys, label) => {
|
|
112
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`${label} must be an object`)
|
|
113
|
+
const actual = Object.keys(value).sort()
|
|
114
|
+
const expected = [...keys].sort()
|
|
115
|
+
if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) {
|
|
116
|
+
throw new Error(`${label} has an open or incomplete schema`)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const providerCliStaticReceipt = (value, label, { setup = false } = {}) => {
|
|
120
|
+
exactKeys(
|
|
121
|
+
value,
|
|
122
|
+
['authorityDigest', 'lockDigest', 'platform', 'role', 'staticVerified', 'status', 'target', 'executablesProbed', 'versions'],
|
|
123
|
+
label,
|
|
124
|
+
)
|
|
125
|
+
if (
|
|
126
|
+
!SHA256_RE.test(value.authorityDigest)
|
|
127
|
+
|| !SHA256_RE.test(value.lockDigest)
|
|
128
|
+
|| !/^(?:darwin|linux)-(?:arm64|x64)$/.test(value.platform)
|
|
129
|
+
|| value.role !== 'consumer'
|
|
130
|
+
|| value.staticVerified !== true
|
|
131
|
+
|| value.executablesProbed !== false
|
|
132
|
+
|| value.versions !== null
|
|
133
|
+
|| typeof value.target !== 'string'
|
|
134
|
+
|| !value.target
|
|
135
|
+
|| !(setup ? ['installed', 'ready'].includes(value.status) : value.status === 'static-verified')
|
|
136
|
+
) throw new Error(`${label} is not a closed static-only provider CLI receipt`)
|
|
137
|
+
return Object.freeze({
|
|
138
|
+
authorityDigest: value.authorityDigest,
|
|
139
|
+
lockDigest: value.lockDigest,
|
|
140
|
+
platform: value.platform,
|
|
141
|
+
role: value.role,
|
|
142
|
+
staticVerified: true,
|
|
143
|
+
executablesProbed: false,
|
|
144
|
+
runtimeCertified: false,
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
const assertSameProviderCliIdentity = (left, right, label) => {
|
|
148
|
+
if (JSON.stringify(left) !== JSON.stringify(right)) {
|
|
149
|
+
throw new Error(`${label} provider CLI identity differs across static verification boundaries`)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const regularFile = (path, label) => {
|
|
153
|
+
const info = lstatSync(path)
|
|
154
|
+
if (info.isSymbolicLink() || !info.isFile() || info.nlink !== 1) throw new Error(`${label} must be one regular non-symlink file`)
|
|
155
|
+
return path
|
|
156
|
+
}
|
|
157
|
+
const sameFileIdentity = (left, right) => left.dev === right.dev && left.ino === right.ino
|
|
158
|
+
const stableRegularFile = (path, label, { links = null, mode = 0o600 } = {}) => {
|
|
159
|
+
const before = lstatSync(path, { bigint: true })
|
|
160
|
+
if (
|
|
161
|
+
!before.isFile()
|
|
162
|
+
|| before.isSymbolicLink()
|
|
163
|
+
|| (Array.isArray(links) && !links.includes(Number(before.nlink)))
|
|
164
|
+
|| Number(before.mode & 0o777n) !== mode
|
|
165
|
+
) throw new Error(`${label} is not one closed regular file`)
|
|
166
|
+
const bytes = readFileSync(path)
|
|
167
|
+
const after = lstatSync(path, { bigint: true })
|
|
168
|
+
if (
|
|
169
|
+
!after.isFile()
|
|
170
|
+
|| after.isSymbolicLink()
|
|
171
|
+
|| !sameFileIdentity(after, before)
|
|
172
|
+
|| after.nlink !== before.nlink
|
|
173
|
+
|| after.mode !== before.mode
|
|
174
|
+
|| after.size !== before.size
|
|
175
|
+
|| after.mtimeNs !== before.mtimeNs
|
|
176
|
+
|| after.ctimeNs !== before.ctimeNs
|
|
177
|
+
|| BigInt(bytes.length) !== before.size
|
|
178
|
+
) throw new Error(`${label} changed while it was read`)
|
|
179
|
+
return { bytes, info: after }
|
|
180
|
+
}
|
|
181
|
+
const writeFsyncedExclusive = (path, bytes, label) => {
|
|
182
|
+
let descriptor
|
|
183
|
+
try {
|
|
184
|
+
descriptor = openSync(path, 'wx', 0o600)
|
|
185
|
+
writeFileSync(descriptor, bytes)
|
|
186
|
+
fsyncSync(descriptor)
|
|
187
|
+
closeSync(descriptor)
|
|
188
|
+
descriptor = undefined
|
|
189
|
+
} finally {
|
|
190
|
+
if (descriptor !== undefined) closeSync(descriptor)
|
|
191
|
+
}
|
|
192
|
+
const written = stableRegularFile(path, label, { links: [1] })
|
|
193
|
+
if (!written.bytes.equals(bytes)) throw new Error(`${label} readback differs from the published bytes`)
|
|
194
|
+
return written.info
|
|
195
|
+
}
|
|
196
|
+
const journalStateNames = (sequence) => {
|
|
197
|
+
if (!Number.isSafeInteger(sequence) || sequence < 0 || sequence > 9_999_999_999_999_999) {
|
|
198
|
+
throw new Error('upgrade journal state sequence is invalid')
|
|
199
|
+
}
|
|
200
|
+
const suffix = String(sequence).padStart(16, '0')
|
|
201
|
+
return {
|
|
202
|
+
final: `state-${suffix}.json`,
|
|
203
|
+
publish: `.state-${suffix}.publish`,
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const publishJournalState = ({ transactionRoot, journal, sequence, previousStateSha256 }) => {
|
|
207
|
+
const stateRoot = join(transactionRoot, JOURNAL_STATE_DIRECTORY)
|
|
208
|
+
if (sequence === 0) mkdirSync(stateRoot, { mode: 0o700 })
|
|
209
|
+
const rootInfo = lstatSync(stateRoot)
|
|
210
|
+
if (
|
|
211
|
+
!rootInfo.isDirectory()
|
|
212
|
+
|| rootInfo.isSymbolicLink()
|
|
213
|
+
|| realpathSync(stateRoot) !== stateRoot
|
|
214
|
+
|| (rootInfo.mode & 0o777) !== 0o700
|
|
215
|
+
) throw new Error('upgrade journal state root is unsafe')
|
|
216
|
+
const envelope = {
|
|
217
|
+
schemaVersion: 1,
|
|
218
|
+
kind: JOURNAL_STATE_KIND,
|
|
219
|
+
sequence,
|
|
220
|
+
previousStateSha256,
|
|
221
|
+
journal: structuredClone(journal),
|
|
222
|
+
}
|
|
223
|
+
const bytes = Buffer.from(`${JSON.stringify(envelope, null, 2)}\n`)
|
|
224
|
+
const names = journalStateNames(sequence)
|
|
225
|
+
const publishPath = join(stateRoot, names.publish)
|
|
226
|
+
const finalPath = join(stateRoot, names.final)
|
|
227
|
+
const publishInfo = writeFsyncedExclusive(publishPath, bytes, `upgrade journal state ${sequence} publication object`)
|
|
228
|
+
linkSync(publishPath, finalPath)
|
|
229
|
+
const final = stableRegularFile(finalPath, `upgrade journal state ${sequence}`, { links: [2] })
|
|
230
|
+
const alias = lstatSync(publishPath, { bigint: true })
|
|
231
|
+
if (
|
|
232
|
+
!sameFileIdentity(final.info, publishInfo)
|
|
233
|
+
|| !sameFileIdentity(final.info, alias)
|
|
234
|
+
|| alias.nlink !== 2n
|
|
235
|
+
) throw new Error(`upgrade journal state ${sequence} publication alias is not exact`)
|
|
236
|
+
return sha256(bytes)
|
|
237
|
+
}
|
|
238
|
+
const loadLatestJournalState = (transactionRoot, lease) => {
|
|
239
|
+
const stateRoot = join(transactionRoot, JOURNAL_STATE_DIRECTORY)
|
|
240
|
+
const rootInfo = lstatSync(stateRoot)
|
|
241
|
+
if (
|
|
242
|
+
!rootInfo.isDirectory()
|
|
243
|
+
|| rootInfo.isSymbolicLink()
|
|
244
|
+
|| realpathSync(stateRoot) !== stateRoot
|
|
245
|
+
|| (rootInfo.mode & 0o777) !== 0o700
|
|
246
|
+
) throw new Error('upgrade recovery journal state root is unsafe')
|
|
247
|
+
const names = readdirSync(stateRoot).sort()
|
|
248
|
+
const finalSequences = names
|
|
249
|
+
.map(name => name.match(JOURNAL_STATE_NAME))
|
|
250
|
+
.filter(Boolean)
|
|
251
|
+
.map(match => Number(match[1]))
|
|
252
|
+
if (!finalSequences.length) throw new Error('upgrade recovery journal has no committed state')
|
|
253
|
+
for (const [index, sequence] of finalSequences.entries()) {
|
|
254
|
+
if (sequence !== index) throw new Error('upgrade recovery journal state sequence is not contiguous')
|
|
255
|
+
}
|
|
256
|
+
const finalSet = new Set(finalSequences)
|
|
257
|
+
const publishSequences = names
|
|
258
|
+
.map(name => name.match(JOURNAL_STATE_PUBLISH_NAME))
|
|
259
|
+
.filter(Boolean)
|
|
260
|
+
.map(match => Number(match[1]))
|
|
261
|
+
const recognized = new Set([
|
|
262
|
+
...finalSequences.map(sequence => journalStateNames(sequence).final),
|
|
263
|
+
...publishSequences.map(sequence => journalStateNames(sequence).publish),
|
|
264
|
+
])
|
|
265
|
+
if (recognized.size !== names.length || names.some(name => !recognized.has(name))) {
|
|
266
|
+
throw new Error('upgrade recovery journal state inventory is open')
|
|
267
|
+
}
|
|
268
|
+
let previousStateSha256 = null
|
|
269
|
+
let latestJournal = null
|
|
270
|
+
for (const sequence of finalSequences) {
|
|
271
|
+
const stateNames = journalStateNames(sequence)
|
|
272
|
+
if (!publishSequences.includes(sequence)) throw new Error(`upgrade recovery journal state ${sequence} lost its publication object`)
|
|
273
|
+
const final = stableRegularFile(join(stateRoot, stateNames.final), `upgrade recovery journal state ${sequence}`, { links: [2] })
|
|
274
|
+
const alias = lstatSync(join(stateRoot, stateNames.publish), { bigint: true })
|
|
275
|
+
if (
|
|
276
|
+
!alias.isFile()
|
|
277
|
+
|| alias.isSymbolicLink()
|
|
278
|
+
|| alias.nlink !== 2n
|
|
279
|
+
|| Number(alias.mode & 0o777n) !== 0o600
|
|
280
|
+
|| !sameFileIdentity(alias, final.info)
|
|
281
|
+
) throw new Error(`upgrade recovery journal state ${sequence} publication alias differs`)
|
|
282
|
+
let envelope
|
|
283
|
+
try { envelope = JSON.parse(final.bytes.toString('utf8')) } catch { throw new Error(`upgrade recovery journal state ${sequence} is invalid JSON`) }
|
|
284
|
+
exactKeys(envelope, ['schemaVersion', 'kind', 'sequence', 'previousStateSha256', 'journal'], `upgrade recovery journal state ${sequence}`)
|
|
285
|
+
if (
|
|
286
|
+
envelope.schemaVersion !== 1
|
|
287
|
+
|| envelope.kind !== JOURNAL_STATE_KIND
|
|
288
|
+
|| envelope.sequence !== sequence
|
|
289
|
+
|| envelope.previousStateSha256 !== previousStateSha256
|
|
290
|
+
) throw new Error(`upgrade recovery journal state ${sequence} chain binding is invalid`)
|
|
291
|
+
if (
|
|
292
|
+
envelope.journal?.repoRoot !== lease.repoRoot
|
|
293
|
+
|| envelope.journal?.transactionId !== lease.transactionId
|
|
294
|
+
|| envelope.journal?.transactionRoot !== lease.transactionRoot
|
|
295
|
+
|| JSON.stringify(envelope.journal?.owner) !== JSON.stringify(lease.owner)
|
|
296
|
+
) throw new Error(`upgrade recovery journal state ${sequence} differs from its immutable lease`)
|
|
297
|
+
latestJournal = envelope.journal
|
|
298
|
+
previousStateSha256 = sha256(final.bytes)
|
|
299
|
+
}
|
|
300
|
+
const trailing = publishSequences.filter(sequence => !finalSet.has(sequence))
|
|
301
|
+
if (
|
|
302
|
+
trailing.length > 1
|
|
303
|
+
|| (trailing.length === 1 && trailing[0] !== finalSequences.at(-1) + 1)
|
|
304
|
+
) throw new Error('upgrade recovery journal has an invalid uncommitted state publication')
|
|
305
|
+
if (trailing.length === 1) {
|
|
306
|
+
const pending = lstatSync(join(stateRoot, journalStateNames(trailing[0]).publish), { bigint: true })
|
|
307
|
+
if (
|
|
308
|
+
!pending.isFile()
|
|
309
|
+
|| pending.isSymbolicLink()
|
|
310
|
+
|| pending.nlink !== 1n
|
|
311
|
+
|| Number(pending.mode & 0o777n) !== 0o600
|
|
312
|
+
) throw new Error('upgrade recovery journal pending state object is unsafe')
|
|
313
|
+
}
|
|
314
|
+
return { journal: latestJournal, sequence: finalSequences.at(-1), stateSha256: previousStateSha256 }
|
|
315
|
+
}
|
|
316
|
+
const journalLeaseBody = ({ transactionRoot, transactionId, owner }) => ({
|
|
317
|
+
schemaVersion: 1,
|
|
318
|
+
kind: JOURNAL_LEASE_KIND,
|
|
319
|
+
repoRoot,
|
|
320
|
+
transactionId,
|
|
321
|
+
transactionRoot,
|
|
322
|
+
owner: structuredClone(owner),
|
|
323
|
+
})
|
|
324
|
+
const publishJournalLease = (lease) => {
|
|
325
|
+
const source = join(lease.transactionRoot, JOURNAL_LEASE_SOURCE)
|
|
326
|
+
const bytes = Buffer.from(`${JSON.stringify(lease, null, 2)}\n`)
|
|
327
|
+
const sourceInfo = writeFsyncedExclusive(source, bytes, 'upgrade journal lease source')
|
|
328
|
+
let linked = false
|
|
329
|
+
try {
|
|
330
|
+
linkSync(source, journalPath)
|
|
331
|
+
linked = true
|
|
332
|
+
const published = stableRegularFile(journalPath, 'published upgrade journal lease', { links: [2] })
|
|
333
|
+
const sourceAfter = lstatSync(source, { bigint: true })
|
|
334
|
+
if (
|
|
335
|
+
!published.bytes.equals(bytes)
|
|
336
|
+
|| !sameFileIdentity(published.info, sourceInfo)
|
|
337
|
+
|| !sameFileIdentity(published.info, sourceAfter)
|
|
338
|
+
|| sourceAfter.nlink !== 2n
|
|
339
|
+
) throw new Error('published upgrade journal lease differs from its capability source')
|
|
340
|
+
return { bytes, info: published.info, source }
|
|
341
|
+
} catch (error) {
|
|
342
|
+
if (linked) {
|
|
343
|
+
try {
|
|
344
|
+
releaseJournalPointer({
|
|
345
|
+
expectedIdentity: { bytes, info: sourceInfo, kind: JOURNAL_LEASE_KIND },
|
|
346
|
+
transactionRoot: lease.transactionRoot,
|
|
347
|
+
label: 'failed upgrade journal lease publication',
|
|
348
|
+
})
|
|
349
|
+
} catch (releaseError) {
|
|
350
|
+
releaseError.preserveTransactionRoot = true
|
|
351
|
+
throw releaseError
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
throw error
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
const assertPublishedJournalLease = (leaseIdentity) => {
|
|
358
|
+
assertNoSymlinkPath(repoRoot, journalPath, 'owned upgrade journal lease', { allowMissing: false })
|
|
359
|
+
const published = stableRegularFile(journalPath, 'owned upgrade journal lease', { links: [2] })
|
|
360
|
+
const source = stableRegularFile(leaseIdentity.source, 'owned upgrade journal lease source', { links: [2] })
|
|
361
|
+
if (
|
|
362
|
+
!sameFileIdentity(published.info, leaseIdentity.info)
|
|
363
|
+
|| !sameFileIdentity(source.info, leaseIdentity.info)
|
|
364
|
+
|| !published.bytes.equals(leaseIdentity.bytes)
|
|
365
|
+
|| !source.bytes.equals(leaseIdentity.bytes)
|
|
366
|
+
) throw new Error('upgrade journal lease ownership changed during transaction')
|
|
367
|
+
return published.info
|
|
368
|
+
}
|
|
369
|
+
const releaseJournalPointer = ({ expectedIdentity, transactionRoot, label }) => {
|
|
370
|
+
assertNoSymlinkPath(repoRoot, dirname(journalPath), `${label} parent`, { allowMissing: false })
|
|
371
|
+
const captureRoot = join(transactionRoot, `.governance-upgrade-lease-release-${randomUUID()}`)
|
|
372
|
+
mkdirSync(captureRoot, { mode: 0o700 })
|
|
373
|
+
const capturedPath = join(captureRoot, 'captured')
|
|
374
|
+
// Rename preserves whichever inode currently occupies the public name. It never overwrites an
|
|
375
|
+
// existing destination because the destination lives in a freshly-created random capability slot.
|
|
376
|
+
renameSync(journalPath, capturedPath)
|
|
377
|
+
let captured
|
|
378
|
+
try {
|
|
379
|
+
captured = stableRegularFile(capturedPath, `${label} captured pointer`, {
|
|
380
|
+
links: expectedIdentity.kind === JOURNAL_LEASE_KIND ? [2] : [1, 2],
|
|
381
|
+
})
|
|
382
|
+
} catch (error) {
|
|
383
|
+
try { linkSync(capturedPath, journalPath) } catch (restoreError) {
|
|
384
|
+
if (restoreError?.code !== 'EEXIST') throw restoreError
|
|
385
|
+
}
|
|
386
|
+
throw new Error(`${label} could not be validated after capture; captured bytes were preserved`, { cause: error })
|
|
387
|
+
}
|
|
388
|
+
if (!sameFileIdentity(captured.info, expectedIdentity.info) || !captured.bytes.equals(expectedIdentity.bytes)) {
|
|
389
|
+
// Restore the unknown bytes with atomic create-if-absent. If another writer has already claimed
|
|
390
|
+
// the public name, both versions remain preserved: one there and one in this capability root.
|
|
391
|
+
try { linkSync(capturedPath, journalPath) } catch (error) {
|
|
392
|
+
if (error?.code !== 'EEXIST') throw error
|
|
393
|
+
}
|
|
394
|
+
throw new Error(`${label} changed before release; substituted bytes were preserved`)
|
|
395
|
+
}
|
|
396
|
+
return capturedPath
|
|
397
|
+
}
|
|
398
|
+
const pathFingerprint = (path, label, { allowHardlinks = false } = {}) => {
|
|
399
|
+
if (!pathEntryExists(path)) return null
|
|
400
|
+
const rows = []
|
|
401
|
+
const visit = (absolute, relativePath = '.') => {
|
|
402
|
+
const info = lstatSync(absolute)
|
|
403
|
+
if (info.isSymbolicLink()) throw new Error(`${label} contains a symbolic link:${relativePath}`)
|
|
404
|
+
const mode = (info.mode & 0o777).toString(8).padStart(3, '0')
|
|
405
|
+
if (info.isDirectory()) {
|
|
406
|
+
rows.push(`tree:${relativePath}:${mode}`)
|
|
407
|
+
for (const name of readdirSync(absolute).sort()) visit(join(absolute, name), relativePath === '.' ? name : `${relativePath}/${name}`)
|
|
408
|
+
return
|
|
409
|
+
}
|
|
410
|
+
if (!info.isFile() || (!allowHardlinks && info.nlink !== 1) || (allowHardlinks && info.nlink > 2)) {
|
|
411
|
+
throw new Error(`${label} contains an unsupported or hard-linked entry:${relativePath}`)
|
|
412
|
+
}
|
|
413
|
+
rows.push(`file:${relativePath}:${mode}:${sha256(readFileSync(absolute))}`)
|
|
414
|
+
}
|
|
415
|
+
visit(path)
|
|
416
|
+
return sha256(`${rows.join('\n')}\n`)
|
|
417
|
+
}
|
|
418
|
+
const nodeModulesFingerprint = (path, label) => {
|
|
419
|
+
if (!pathEntryExists(path)) return null
|
|
420
|
+
const rows = []
|
|
421
|
+
const stableIdentity = (before, after) => (
|
|
422
|
+
before.dev === after.dev
|
|
423
|
+
&& before.ino === after.ino
|
|
424
|
+
&& before.mode === after.mode
|
|
425
|
+
&& before.size === after.size
|
|
426
|
+
&& before.mtimeNs === after.mtimeNs
|
|
427
|
+
&& before.ctimeNs === after.ctimeNs
|
|
428
|
+
&& before.nlink === after.nlink
|
|
429
|
+
)
|
|
430
|
+
const directoryNames = (absolute, relativePath) => readdirSync(absolute, { encoding: 'buffer' })
|
|
431
|
+
.map((bytes) => {
|
|
432
|
+
const name = bytes.toString('utf8')
|
|
433
|
+
if (
|
|
434
|
+
!Buffer.from(name, 'utf8').equals(bytes)
|
|
435
|
+
|| !name
|
|
436
|
+
|| name === '.'
|
|
437
|
+
|| name === '..'
|
|
438
|
+
|| name.includes('/')
|
|
439
|
+
|| name.includes('\0')
|
|
440
|
+
) throw new Error(`${label} contains an unsupported directory name:${relativePath}`)
|
|
441
|
+
return { bytes, name }
|
|
442
|
+
})
|
|
443
|
+
.sort((left, right) => Buffer.compare(left.bytes, right.bytes))
|
|
444
|
+
const visit = (absolute, relativePath) => {
|
|
445
|
+
const before = lstatSync(absolute, { bigint: true })
|
|
446
|
+
const mode = Number(before.mode & 0o777n)
|
|
447
|
+
if (before.isSymbolicLink()) {
|
|
448
|
+
if (before.nlink !== 1n) throw new Error(`${label} contains a hard-linked symbolic link:${relativePath}`)
|
|
449
|
+
const target = readlinkSync(absolute, { encoding: 'buffer' })
|
|
450
|
+
const after = lstatSync(absolute, { bigint: true })
|
|
451
|
+
if (!after.isSymbolicLink() || !stableIdentity(before, after)) {
|
|
452
|
+
throw new Error(`${label} symbolic link changed while hashing:${relativePath}`)
|
|
453
|
+
}
|
|
454
|
+
rows.push(JSON.stringify({
|
|
455
|
+
kind: 'symlink',
|
|
456
|
+
mode,
|
|
457
|
+
path: relativePath,
|
|
458
|
+
targetSha256: sha256(target),
|
|
459
|
+
targetBytes: target.length,
|
|
460
|
+
}))
|
|
461
|
+
return
|
|
462
|
+
}
|
|
463
|
+
if (before.isDirectory()) {
|
|
464
|
+
const names = directoryNames(absolute, relativePath)
|
|
465
|
+
rows.push(JSON.stringify({ kind: 'directory', mode, path: relativePath }))
|
|
466
|
+
for (const { name } of names) {
|
|
467
|
+
visit(join(absolute, name), relativePath === '.' ? name : `${relativePath}/${name}`)
|
|
468
|
+
}
|
|
469
|
+
const namesAfter = directoryNames(absolute, relativePath)
|
|
470
|
+
const after = lstatSync(absolute, { bigint: true })
|
|
471
|
+
if (
|
|
472
|
+
!after.isDirectory()
|
|
473
|
+
|| !stableIdentity(before, after)
|
|
474
|
+
|| names.length !== namesAfter.length
|
|
475
|
+
|| names.some((entry, index) => !entry.bytes.equals(namesAfter[index].bytes))
|
|
476
|
+
) throw new Error(`${label} directory changed while hashing:${relativePath}`)
|
|
477
|
+
return
|
|
478
|
+
}
|
|
479
|
+
if (!before.isFile() || before.nlink !== 1n) {
|
|
480
|
+
throw new Error(`${label} contains an unsupported or hard-linked entry:${relativePath}`)
|
|
481
|
+
}
|
|
482
|
+
let descriptor = null
|
|
483
|
+
let opened
|
|
484
|
+
let openedAfter
|
|
485
|
+
let bytes
|
|
486
|
+
try {
|
|
487
|
+
const noFollow = typeof fsConstants.O_NOFOLLOW === 'number' ? fsConstants.O_NOFOLLOW : 0
|
|
488
|
+
descriptor = openSync(absolute, fsConstants.O_RDONLY | noFollow)
|
|
489
|
+
opened = fstatSync(descriptor, { bigint: true })
|
|
490
|
+
if (!opened.isFile() || opened.nlink !== 1n || !stableIdentity(before, opened)) {
|
|
491
|
+
throw new Error(`${label} file changed before hashing:${relativePath}`)
|
|
492
|
+
}
|
|
493
|
+
bytes = readFileSync(descriptor)
|
|
494
|
+
openedAfter = fstatSync(descriptor, { bigint: true })
|
|
495
|
+
} finally {
|
|
496
|
+
if (descriptor !== null) closeSync(descriptor)
|
|
497
|
+
}
|
|
498
|
+
const after = lstatSync(absolute, { bigint: true })
|
|
499
|
+
if (
|
|
500
|
+
BigInt(bytes.length) !== before.size
|
|
501
|
+
|| !openedAfter.isFile()
|
|
502
|
+
|| !stableIdentity(opened, openedAfter)
|
|
503
|
+
|| !after.isFile()
|
|
504
|
+
|| !stableIdentity(before, after)
|
|
505
|
+
) throw new Error(`${label} file changed while hashing:${relativePath}`)
|
|
506
|
+
rows.push(JSON.stringify({
|
|
507
|
+
bytes: bytes.length,
|
|
508
|
+
contentSha256: sha256(bytes),
|
|
509
|
+
kind: 'file',
|
|
510
|
+
mode,
|
|
511
|
+
path: relativePath,
|
|
512
|
+
}))
|
|
513
|
+
}
|
|
514
|
+
const root = lstatSync(path)
|
|
515
|
+
if (!root.isDirectory() || root.isSymbolicLink() || realpathSync(path) !== resolve(path)) {
|
|
516
|
+
throw new Error(`${label} root must be one real directory`)
|
|
517
|
+
}
|
|
518
|
+
visit(path, '.')
|
|
519
|
+
return sha256(`qijenchen-node-modules-tree-v1\0${rows.join('\n')}\n`)
|
|
520
|
+
}
|
|
521
|
+
const attachBeforeState = (entry, label) => ({
|
|
522
|
+
...entry,
|
|
523
|
+
beforeFingerprint: entry.present ? pathFingerprint(entry.backup, `${label} before-image`) : null,
|
|
524
|
+
afterRecorded: false,
|
|
525
|
+
afterPresent: false,
|
|
526
|
+
afterFingerprint: null,
|
|
527
|
+
replacementStarted: false,
|
|
528
|
+
replacementCompleted: false,
|
|
529
|
+
})
|
|
530
|
+
const captureCandidateState = (entry, candidatePath, label) => {
|
|
531
|
+
const present = pathEntryExists(candidatePath)
|
|
532
|
+
return {
|
|
533
|
+
...entry,
|
|
534
|
+
afterRecorded: true,
|
|
535
|
+
afterPresent: present,
|
|
536
|
+
afterFingerprint: present ? pathFingerprint(candidatePath, `${label} candidate after-image`) : null,
|
|
537
|
+
replacementStarted: false,
|
|
538
|
+
replacementCompleted: false,
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
const validateStateRecord = (entry, label) => {
|
|
542
|
+
if (
|
|
543
|
+
typeof entry.present !== 'boolean'
|
|
544
|
+
|| typeof entry.afterRecorded !== 'boolean'
|
|
545
|
+
|| typeof entry.afterPresent !== 'boolean'
|
|
546
|
+
|| typeof entry.replacementStarted !== 'boolean'
|
|
547
|
+
|| typeof entry.replacementCompleted !== 'boolean'
|
|
548
|
+
) {
|
|
549
|
+
throw new Error(`${label} state flags are invalid`)
|
|
550
|
+
}
|
|
551
|
+
if (entry.present ? !SHA256_RE.test(entry.beforeFingerprint || '') : entry.beforeFingerprint !== null) {
|
|
552
|
+
throw new Error(`${label} before-image fingerprint is invalid`)
|
|
553
|
+
}
|
|
554
|
+
if (!entry.afterRecorded) {
|
|
555
|
+
if (
|
|
556
|
+
entry.afterPresent !== false
|
|
557
|
+
|| entry.afterFingerprint !== null
|
|
558
|
+
|| entry.replacementStarted
|
|
559
|
+
|| entry.replacementCompleted
|
|
560
|
+
) throw new Error(`${label} uncommitted after-image is invalid`)
|
|
561
|
+
} else if (entry.afterPresent ? !SHA256_RE.test(entry.afterFingerprint || '') : entry.afterFingerprint !== null) {
|
|
562
|
+
throw new Error(`${label} after-image fingerprint is invalid`)
|
|
563
|
+
}
|
|
564
|
+
if (entry.replacementCompleted && !entry.replacementStarted) {
|
|
565
|
+
throw new Error(`${label} completed replacement was never started`)
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
const NODE_MODULES_STATE_KEYS = Object.freeze([
|
|
569
|
+
'source', 'backup', 'present', 'beforeFingerprint',
|
|
570
|
+
'afterRecorded', 'afterPresent', 'afterFingerprint',
|
|
571
|
+
'replacementStarted', 'replacementCompleted',
|
|
572
|
+
])
|
|
573
|
+
const validateNodeModulesStateRecord = (entry, label) => {
|
|
574
|
+
exactKeys(entry, NODE_MODULES_STATE_KEYS, label)
|
|
575
|
+
validateStateRecord(entry, label)
|
|
576
|
+
if (entry.afterRecorded && entry.afterPresent !== true) {
|
|
577
|
+
throw new Error(`${label} must bind one present candidate node_modules tree`)
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
const captureNodeModulesAfterState = (entry, candidatePath, label) => {
|
|
581
|
+
const fingerprint = nodeModulesFingerprint(candidatePath, `${label} candidate after-image`)
|
|
582
|
+
if (!SHA256_RE.test(fingerprint || '')) throw new Error(`${label} candidate node_modules tree is absent`)
|
|
583
|
+
entry.afterRecorded = true
|
|
584
|
+
entry.afterPresent = true
|
|
585
|
+
entry.afterFingerprint = fingerprint
|
|
586
|
+
entry.replacementStarted = false
|
|
587
|
+
entry.replacementCompleted = false
|
|
588
|
+
return entry
|
|
589
|
+
}
|
|
590
|
+
const inspectNodeModulesJournalState = (entry, candidatePath, label) => {
|
|
591
|
+
validateNodeModulesStateRecord(entry, `${label} state`)
|
|
592
|
+
const sourcePresent = pathEntryExists(entry.source)
|
|
593
|
+
const backupPresent = pathEntryExists(entry.backup)
|
|
594
|
+
const candidatePresent = candidatePath !== null && pathEntryExists(candidatePath)
|
|
595
|
+
const sourceFingerprint = sourcePresent
|
|
596
|
+
? nodeModulesFingerprint(entry.source, `${label} live source`)
|
|
597
|
+
: null
|
|
598
|
+
const backupFingerprint = backupPresent
|
|
599
|
+
? nodeModulesFingerprint(entry.backup, `${label} recovery backup`)
|
|
600
|
+
: null
|
|
601
|
+
const candidateFingerprint = candidatePresent
|
|
602
|
+
? nodeModulesFingerprint(candidatePath, `${label} candidate source`)
|
|
603
|
+
: null
|
|
604
|
+
|
|
605
|
+
if (backupPresent && (!entry.present || backupFingerprint !== entry.beforeFingerprint)) {
|
|
606
|
+
throw new Error(`${label} recovery backup is an unknown node_modules image`)
|
|
607
|
+
}
|
|
608
|
+
if (candidatePresent && (!entry.afterRecorded || candidateFingerprint !== entry.afterFingerprint)) {
|
|
609
|
+
throw new Error(`${label} candidate source is an unknown node_modules image`)
|
|
610
|
+
}
|
|
611
|
+
const sourceIsBefore = sourcePresent
|
|
612
|
+
&& entry.present
|
|
613
|
+
&& sourceFingerprint === entry.beforeFingerprint
|
|
614
|
+
const sourceIsAfter = sourcePresent
|
|
615
|
+
&& entry.afterRecorded
|
|
616
|
+
&& sourceFingerprint === entry.afterFingerprint
|
|
617
|
+
const exactBeforeState = sourceIsBefore && !backupPresent
|
|
618
|
+
const exactAfterState = sourceIsAfter
|
|
619
|
+
&& entry.replacementStarted
|
|
620
|
+
&& backupPresent === entry.present
|
|
621
|
+
&& !candidatePresent
|
|
622
|
+
const controlledGap = !sourcePresent
|
|
623
|
+
&& entry.replacementStarted
|
|
624
|
+
&& !entry.replacementCompleted
|
|
625
|
+
&& (
|
|
626
|
+
(entry.present && backupPresent)
|
|
627
|
+
|| (!entry.present && !backupPresent)
|
|
628
|
+
)
|
|
629
|
+
const originalAbsence = !sourcePresent
|
|
630
|
+
&& !entry.present
|
|
631
|
+
&& !backupPresent
|
|
632
|
+
&& !entry.replacementCompleted
|
|
633
|
+
if (!(exactBeforeState || exactAfterState || controlledGap || originalAbsence)) {
|
|
634
|
+
throw new Error(`${label} recovery blocked by unknown post-journal node_modules content`)
|
|
635
|
+
}
|
|
636
|
+
if (entry.replacementCompleted && !exactAfterState && !exactBeforeState) {
|
|
637
|
+
throw new Error(`${label} completed replacement has no exact live image`)
|
|
638
|
+
}
|
|
639
|
+
return {
|
|
640
|
+
backupFingerprint,
|
|
641
|
+
backupPresent,
|
|
642
|
+
candidateFingerprint,
|
|
643
|
+
candidatePresent,
|
|
644
|
+
exactAfterState,
|
|
645
|
+
exactBeforeState,
|
|
646
|
+
sourceFingerprint,
|
|
647
|
+
sourceIsAfter,
|
|
648
|
+
sourceIsBefore,
|
|
649
|
+
sourcePresent,
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
const assertNodeModulesBeforeState = (entry, candidatePath, label) => {
|
|
653
|
+
const state = inspectNodeModulesJournalState(entry, candidatePath, label)
|
|
654
|
+
if (!state.exactBeforeState && !(entry.present === false && state.sourcePresent === false && state.backupPresent === false)) {
|
|
655
|
+
throw new Error(`${label} base changed during disposable reconstruction`)
|
|
656
|
+
}
|
|
657
|
+
if (!entry.afterRecorded || !state.candidatePresent) {
|
|
658
|
+
throw new Error(`${label} candidate after-image is not capability-bound`)
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
const assertNodeModulesCommitState = (entry, label) => {
|
|
662
|
+
const state = inspectNodeModulesJournalState(entry, null, label)
|
|
663
|
+
if (!entry.replacementCompleted || !state.exactAfterState) {
|
|
664
|
+
throw new Error(`${label} is not the exact completed candidate image`)
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
const restoreNodeModulesFromJournal = (entry, candidatePath, label) => {
|
|
668
|
+
const state = inspectNodeModulesJournalState(entry, candidatePath, label)
|
|
669
|
+
if (state.exactBeforeState || (!entry.present && !state.sourcePresent && !state.backupPresent)) return
|
|
670
|
+
if (state.sourcePresent) {
|
|
671
|
+
if (!state.sourceIsAfter) throw new Error(`${label} refuses to remove an unknown live node_modules image`)
|
|
672
|
+
safeRemoveRepositoryTarget('node_modules')
|
|
673
|
+
}
|
|
674
|
+
if (entry.present) {
|
|
675
|
+
if (!pathEntryExists(entry.backup)) throw new Error(`${label} lost its exact predecessor node_modules image`)
|
|
676
|
+
renameSync(entry.backup, entry.source)
|
|
677
|
+
}
|
|
678
|
+
const finalPresent = pathEntryExists(entry.source)
|
|
679
|
+
const finalFingerprint = finalPresent
|
|
680
|
+
? nodeModulesFingerprint(entry.source, `${label} restored predecessor`)
|
|
681
|
+
: null
|
|
682
|
+
if (finalPresent !== entry.present || finalFingerprint !== entry.beforeFingerprint) {
|
|
683
|
+
throw new Error(`${label} did not restore the exact predecessor node_modules image`)
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
const assertRecoverableLiveState = (
|
|
687
|
+
entry,
|
|
688
|
+
label,
|
|
689
|
+
transientPath = null,
|
|
690
|
+
transientCandidatePath = null,
|
|
691
|
+
transientRestorePath = null,
|
|
692
|
+
transientRecoveryMovedPath = null,
|
|
693
|
+
) => {
|
|
694
|
+
const movedBeforePresent = transientPath !== null && pathEntryExists(transientPath)
|
|
695
|
+
if (movedBeforePresent) {
|
|
696
|
+
if (!entry.present || !entry.replacementStarted) {
|
|
697
|
+
throw new Error(`recovery has an impossible moved before-image:${entry.path}`)
|
|
698
|
+
}
|
|
699
|
+
if (pathFingerprint(transientPath, `${label} moved before-image`) !== entry.beforeFingerprint) {
|
|
700
|
+
throw new Error(`recovery blocked by changed moved before-image:${entry.path}`)
|
|
701
|
+
}
|
|
702
|
+
} else if (entry.present && entry.replacementCompleted) {
|
|
703
|
+
throw new Error(`recovery lost the completed replacement before-image:${entry.path}`)
|
|
704
|
+
}
|
|
705
|
+
const present = pathEntryExists(entry.source)
|
|
706
|
+
let linkedCandidate = false
|
|
707
|
+
const possibleAliases = [transientCandidatePath, transientRestorePath]
|
|
708
|
+
.filter((path) => path !== null && pathEntryExists(path))
|
|
709
|
+
if (present && possibleAliases.length) {
|
|
710
|
+
const sourceInfo = lstatSync(entry.source)
|
|
711
|
+
linkedCandidate = sourceInfo.isFile()
|
|
712
|
+
&& !sourceInfo.isSymbolicLink()
|
|
713
|
+
&& sourceInfo.nlink === 2
|
|
714
|
+
&& possibleAliases.some((alias) => {
|
|
715
|
+
const aliasInfo = lstatSync(alias)
|
|
716
|
+
return aliasInfo.isFile()
|
|
717
|
+
&& !aliasInfo.isSymbolicLink()
|
|
718
|
+
&& aliasInfo.nlink === 2
|
|
719
|
+
&& sourceInfo.dev === aliasInfo.dev
|
|
720
|
+
&& sourceInfo.ino === aliasInfo.ino
|
|
721
|
+
})
|
|
722
|
+
}
|
|
723
|
+
const fingerprint = present
|
|
724
|
+
? pathFingerprint(entry.source, `${label} live target`, { allowHardlinks: linkedCandidate })
|
|
725
|
+
: null
|
|
726
|
+
const beforeMatches = present === entry.present && fingerprint === entry.beforeFingerprint
|
|
727
|
+
const afterMatches = entry.afterRecorded && present === entry.afterPresent && fingerprint === entry.afterFingerprint
|
|
728
|
+
const candidatePresent = transientCandidatePath !== null && pathEntryExists(transientCandidatePath)
|
|
729
|
+
const afterHasCapabilityEvidence = entry.replacementCompleted
|
|
730
|
+
|| (
|
|
731
|
+
entry.replacementStarted
|
|
732
|
+
&& (
|
|
733
|
+
entry.present
|
|
734
|
+
? movedBeforePresent
|
|
735
|
+
: !entry.afterPresent
|
|
736
|
+
|| !candidatePresent
|
|
737
|
+
|| (present && sameRegularInode(entry.source, transientCandidatePath))
|
|
738
|
+
)
|
|
739
|
+
)
|
|
740
|
+
const controlledTransientAbsence = entry.replacementStarted
|
|
741
|
+
&& !entry.replacementCompleted
|
|
742
|
+
&& entry.present
|
|
743
|
+
&& !present
|
|
744
|
+
&& movedBeforePresent
|
|
745
|
+
const controlledRecoveryAbsence = entry.replacementStarted
|
|
746
|
+
&& !present
|
|
747
|
+
&& transientRecoveryMovedPath !== null
|
|
748
|
+
&& pathEntryExists(transientRecoveryMovedPath)
|
|
749
|
+
&& (
|
|
750
|
+
knownFingerprint(
|
|
751
|
+
entry,
|
|
752
|
+
transientRecoveryMovedPath,
|
|
753
|
+
`${label} recovery-moved image`,
|
|
754
|
+
transientCandidatePath,
|
|
755
|
+
),
|
|
756
|
+
true
|
|
757
|
+
)
|
|
758
|
+
const validState = beforeMatches
|
|
759
|
+
|| (afterMatches && afterHasCapabilityEvidence)
|
|
760
|
+
|| controlledTransientAbsence
|
|
761
|
+
|| controlledRecoveryAbsence
|
|
762
|
+
if (!validState) {
|
|
763
|
+
throw new Error(`recovery blocked by unknown post-journal content:${entry.path}`)
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
const assertBeforeLiveState = (entry, label) => {
|
|
767
|
+
const present = pathEntryExists(entry.source)
|
|
768
|
+
const fingerprint = present ? pathFingerprint(entry.source, `${label} live before-image`) : null
|
|
769
|
+
if (present !== entry.present || fingerprint !== entry.beforeFingerprint) {
|
|
770
|
+
throw new Error(`transaction base changed during disposable reconstruction:${entry.path}`)
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
const sameRegularInode = (left, right) => {
|
|
774
|
+
if (!pathEntryExists(left) || !pathEntryExists(right)) return false
|
|
775
|
+
const leftInfo = lstatSync(left)
|
|
776
|
+
const rightInfo = lstatSync(right)
|
|
777
|
+
return leftInfo.isFile()
|
|
778
|
+
&& rightInfo.isFile()
|
|
779
|
+
&& !leftInfo.isSymbolicLink()
|
|
780
|
+
&& !rightInfo.isSymbolicLink()
|
|
781
|
+
&& leftInfo.dev === rightInfo.dev
|
|
782
|
+
&& leftInfo.ino === rightInfo.ino
|
|
783
|
+
}
|
|
784
|
+
const knownFingerprint = (entry, path, label, aliasPath = null) => {
|
|
785
|
+
const allowHardlinks = aliasPath !== null && sameRegularInode(path, aliasPath)
|
|
786
|
+
const fingerprint = pathFingerprint(path, label, { allowHardlinks })
|
|
787
|
+
if (fingerprint !== entry.beforeFingerprint && (!entry.afterRecorded || fingerprint !== entry.afterFingerprint)) {
|
|
788
|
+
throw new Error(`recovery blocked by unknown moved content:${entry.path}`)
|
|
789
|
+
}
|
|
790
|
+
return fingerprint
|
|
791
|
+
}
|
|
792
|
+
const recoveryArtifactPaths = (transactionRoot, category, index) => {
|
|
793
|
+
const slot = join(transactionRoot, 'recovery-staging', category, String(index))
|
|
794
|
+
return {
|
|
795
|
+
slot,
|
|
796
|
+
restoreTemporary: join(slot, 'restore.tmp'),
|
|
797
|
+
restore: join(slot, 'restore'),
|
|
798
|
+
moved: join(slot, 'moved'),
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
const validateRecoveryArtifactSlot = ({ entry, transactionRoot, category, index, label }) => {
|
|
802
|
+
const paths = recoveryArtifactPaths(transactionRoot, category, index)
|
|
803
|
+
if (!pathEntryExists(paths.slot)) return paths
|
|
804
|
+
const slotInfo = lstatSync(paths.slot)
|
|
805
|
+
if (!slotInfo.isDirectory() || slotInfo.isSymbolicLink() || realpathSync(paths.slot) !== paths.slot) {
|
|
806
|
+
throw new Error(`${label} recovery slot is unsafe`)
|
|
807
|
+
}
|
|
808
|
+
const allowed = new Set(['restore.tmp', 'restore', 'moved'])
|
|
809
|
+
for (const name of readdirSync(paths.slot)) {
|
|
810
|
+
if (!allowed.has(name)) throw new Error(`${label} recovery slot has unowned content:${name}`)
|
|
811
|
+
}
|
|
812
|
+
if (pathEntryExists(paths.restoreTemporary)) {
|
|
813
|
+
if (!entry.present) throw new Error(`${label} absent before-image has a temporary recovery copy`)
|
|
814
|
+
assertSafeTree(transactionRoot, paths.restoreTemporary, `${label} temporary recovery copy`)
|
|
815
|
+
const temporaryInfo = lstatSync(paths.restoreTemporary)
|
|
816
|
+
const backupInfo = lstatSync(entry.backup)
|
|
817
|
+
if (temporaryInfo.isDirectory() !== backupInfo.isDirectory() || temporaryInfo.isFile() !== backupInfo.isFile()) {
|
|
818
|
+
throw new Error(`${label} temporary recovery copy has the wrong kind`)
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
if (pathEntryExists(paths.restore)) {
|
|
822
|
+
if (!entry.present) throw new Error(`${label} absent before-image has a recovery copy`)
|
|
823
|
+
const linkedLive = sameRegularInode(entry.source, paths.restore)
|
|
824
|
+
if (linkedLive) {
|
|
825
|
+
const restoreInfo = lstatSync(paths.restore)
|
|
826
|
+
const sourceInfo = lstatSync(entry.source)
|
|
827
|
+
if (restoreInfo.nlink !== 2 || sourceInfo.nlink !== 2) {
|
|
828
|
+
throw new Error(`${label} recovery publication has an unexpected hard-link shape`)
|
|
829
|
+
}
|
|
830
|
+
} else {
|
|
831
|
+
assertSafeTree(transactionRoot, paths.restore, `${label} recovery copy`)
|
|
832
|
+
}
|
|
833
|
+
if (pathFingerprint(paths.restore, `${label} recovery copy`, { allowHardlinks: linkedLive }) !== entry.beforeFingerprint) {
|
|
834
|
+
throw new Error(`${label} recovery copy differs from before-image`)
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
if (pathEntryExists(paths.moved)) {
|
|
838
|
+
const candidatePath = join(transactionRoot, 'candidate-staging', entry.path)
|
|
839
|
+
const linkedCandidate = sameRegularInode(paths.moved, candidatePath)
|
|
840
|
+
if (linkedCandidate) {
|
|
841
|
+
const movedInfo = lstatSync(paths.moved)
|
|
842
|
+
const candidateInfo = lstatSync(candidatePath)
|
|
843
|
+
if (movedInfo.nlink !== 2 || candidateInfo.nlink !== 2) {
|
|
844
|
+
throw new Error(`${label} recovery moved image has an unexpected hard-link shape`)
|
|
845
|
+
}
|
|
846
|
+
} else {
|
|
847
|
+
assertSafeTree(transactionRoot, paths.moved, `${label} recovery moved image`)
|
|
848
|
+
}
|
|
849
|
+
knownFingerprint(entry, paths.moved, `${label} recovery moved image`, candidatePath)
|
|
850
|
+
}
|
|
851
|
+
return paths
|
|
852
|
+
}
|
|
853
|
+
const restoreEntryAtomically = ({ entry, transactionRoot, category, index, label }) => {
|
|
854
|
+
const { slot, restoreTemporary, restore: restorePath, moved: movedPath } = recoveryArtifactPaths(
|
|
855
|
+
transactionRoot,
|
|
856
|
+
category,
|
|
857
|
+
index,
|
|
858
|
+
)
|
|
859
|
+
const candidatePath = join(transactionRoot, 'candidate-staging', entry.path)
|
|
860
|
+
const activeMovedPath = join(transactionRoot, 'live-staging', category, String(index))
|
|
861
|
+
mkdirSync(slot, { recursive: true })
|
|
862
|
+
assertNoSymlinkPath(transactionRoot, slot, `${label} recovery slot`, { allowMissing: false })
|
|
863
|
+
validateRecoveryArtifactSlot({ entry, transactionRoot, category, index, label })
|
|
864
|
+
|
|
865
|
+
// A crash can land between link(2) and unlinking the restore alias. Close that exact inode pair
|
|
866
|
+
// first; no other hard-link shape is accepted as a recovery state.
|
|
867
|
+
if (sameRegularInode(entry.source, restorePath)) rmSync(restorePath)
|
|
868
|
+
|
|
869
|
+
if (pathEntryExists(movedPath)) {
|
|
870
|
+
knownFingerprint(entry, movedPath, `${label} prior recovery move`, candidatePath)
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
assertRecoverableLiveState(entry, label, activeMovedPath, candidatePath, restorePath, movedPath)
|
|
874
|
+
|
|
875
|
+
const sourcePresent = pathEntryExists(entry.source)
|
|
876
|
+
const sourceFingerprint = sourcePresent
|
|
877
|
+
? pathFingerprint(entry.source, `${label} recovery live target`, {
|
|
878
|
+
allowHardlinks: sameRegularInode(entry.source, candidatePath),
|
|
879
|
+
})
|
|
880
|
+
: null
|
|
881
|
+
const beforeMatches = sourcePresent === entry.present && sourceFingerprint === entry.beforeFingerprint
|
|
882
|
+
if (beforeMatches) return
|
|
883
|
+
|
|
884
|
+
if (entry.present) {
|
|
885
|
+
if (pathEntryExists(restoreTemporary)) rmSync(restoreTemporary, { recursive: true, force: true })
|
|
886
|
+
if (!pathEntryExists(restorePath)) {
|
|
887
|
+
cpSync(entry.backup, restoreTemporary, { recursive: true, verbatimSymlinks: true })
|
|
888
|
+
if (pathFingerprint(restoreTemporary, `${label} complete temporary recovery copy`) !== entry.beforeFingerprint) {
|
|
889
|
+
throw new Error(`recovery copy differs from before-image:${entry.path}`)
|
|
890
|
+
}
|
|
891
|
+
renameSync(restoreTemporary, restorePath)
|
|
892
|
+
}
|
|
893
|
+
} else if (pathEntryExists(restorePath) || pathEntryExists(restoreTemporary)) {
|
|
894
|
+
throw new Error(`absent before-image has an unexpected recovery copy:${entry.path}`)
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
if (pathEntryExists(movedPath)) {
|
|
898
|
+
knownFingerprint(entry, movedPath, `${label} prior recovery move`, candidatePath)
|
|
899
|
+
if (pathEntryExists(entry.source)) {
|
|
900
|
+
throw new Error(`recovery target was recreated after its known image was preserved:${entry.path}`)
|
|
901
|
+
}
|
|
902
|
+
} else if (pathEntryExists(entry.source)) {
|
|
903
|
+
renameSync(entry.source, movedPath)
|
|
904
|
+
try {
|
|
905
|
+
knownFingerprint(entry, movedPath, `${label} atomically moved recovery image`, candidatePath)
|
|
906
|
+
} catch (error) {
|
|
907
|
+
if (!pathEntryExists(entry.source)) renameSync(movedPath, entry.source)
|
|
908
|
+
throw error
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
if (pathEntryExists(entry.source)) {
|
|
913
|
+
throw new Error(`recovery destination appeared during restore:${entry.path}`)
|
|
914
|
+
}
|
|
915
|
+
if (entry.present) {
|
|
916
|
+
const restoreInfo = lstatSync(restorePath)
|
|
917
|
+
if (restoreInfo.isFile() && !restoreInfo.isSymbolicLink()) {
|
|
918
|
+
linkSync(restorePath, entry.source)
|
|
919
|
+
rmSync(restorePath)
|
|
920
|
+
} else {
|
|
921
|
+
renameSync(restorePath, entry.source)
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
const finalPresent = pathEntryExists(entry.source)
|
|
925
|
+
const finalFingerprint = finalPresent ? pathFingerprint(entry.source, `${label} restored before-image`) : null
|
|
926
|
+
if (finalPresent !== entry.present || finalFingerprint !== entry.beforeFingerprint) {
|
|
927
|
+
throw new Error(`recovery did not converge to before-image:${entry.path}`)
|
|
928
|
+
}
|
|
929
|
+
assertRecoverableLiveState(entry, label, activeMovedPath, candidatePath, restorePath, movedPath)
|
|
930
|
+
}
|
|
931
|
+
const expectedTransactionRoot = (transactionId) => join(dirname(repoRoot), `.governance-upgrade-${transactionId}`)
|
|
932
|
+
const validateTransactionRoot = (rawRoot, transactionId, owner) => {
|
|
933
|
+
if (!UUID_RE.test(transactionId || '')) throw new Error('journal transactionId is invalid')
|
|
934
|
+
const expected = expectedTransactionRoot(transactionId)
|
|
935
|
+
if (typeof rawRoot !== 'string' || rawRoot !== expected || resolve(rawRoot) !== expected) {
|
|
936
|
+
throw new Error('journal transactionRoot is not the exact capability-bound sibling path')
|
|
937
|
+
}
|
|
938
|
+
const info = lstatSync(expected)
|
|
939
|
+
if (!info.isDirectory() || info.isSymbolicLink() || realpathSync(expected) !== expected) {
|
|
940
|
+
throw new Error('journal transactionRoot must be a real sibling directory')
|
|
941
|
+
}
|
|
942
|
+
const markerPath = join(expected, OWNER_MARKER)
|
|
943
|
+
regularFile(markerPath, 'transaction owner marker')
|
|
944
|
+
const marker = JSON.parse(readFileSync(markerPath, 'utf8'))
|
|
945
|
+
exactKeys(marker, ['schemaVersion', 'repoRoot', 'transactionId', 'owner'], 'transaction owner marker')
|
|
946
|
+
exactKeys(marker.owner, ['host', 'pid'], 'transaction owner marker owner')
|
|
947
|
+
if (
|
|
948
|
+
marker.schemaVersion !== 2
|
|
949
|
+
|| marker.repoRoot !== repoRoot
|
|
950
|
+
|| marker.transactionId !== transactionId
|
|
951
|
+
|| JSON.stringify(marker.owner) !== JSON.stringify(owner)
|
|
952
|
+
) {
|
|
953
|
+
throw new Error('transaction owner marker does not bind this repository and capability')
|
|
954
|
+
}
|
|
955
|
+
return expected
|
|
956
|
+
}
|
|
957
|
+
const safeRemoveRepositoryTarget = (relativePath) => {
|
|
958
|
+
const normalized = normalizeRepositoryRelative(relativePath, 'transaction target')
|
|
959
|
+
const target = join(repoRoot, normalized)
|
|
960
|
+
// Only the final target may have been replaced by the interrupted install. rmSync removes a
|
|
961
|
+
// final symlink itself; every parent remains inside the canonical repository and non-symlink.
|
|
962
|
+
assertNoSymlinkPath(repoRoot, dirname(target), `transaction target parent:${normalized}`, { allowMissing: false })
|
|
963
|
+
if (pathEntryExists(target)) {
|
|
964
|
+
rmSync(target, { recursive: true, force: true })
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
const validateRecoveryJournal = () => {
|
|
968
|
+
assertNoSymlinkPath(repoRoot, journalPath, 'upgrade recovery journal', { allowMissing: false })
|
|
969
|
+
const pointer = stableRegularFile(journalPath, 'upgrade recovery journal pointer', { links: [1, 2] })
|
|
970
|
+
let pointerValue
|
|
971
|
+
try { pointerValue = JSON.parse(pointer.bytes.toString('utf8')) } catch { throw new Error('upgrade recovery journal pointer is invalid JSON') }
|
|
972
|
+
const versioned = pointerValue?.kind === JOURNAL_LEASE_KIND
|
|
973
|
+
let journal
|
|
974
|
+
let journalInfo = pointer.info
|
|
975
|
+
let transactionRoot
|
|
976
|
+
if (versioned) {
|
|
977
|
+
exactKeys(pointerValue, ['schemaVersion', 'kind', 'repoRoot', 'transactionId', 'transactionRoot', 'owner'], 'upgrade recovery journal lease')
|
|
978
|
+
if (pointerValue.schemaVersion !== 1 || pointerValue.repoRoot !== repoRoot) {
|
|
979
|
+
throw new Error('upgrade recovery journal lease repository/schema binding mismatch')
|
|
980
|
+
}
|
|
981
|
+
} else {
|
|
982
|
+
journal = pointerValue
|
|
983
|
+
}
|
|
984
|
+
const binding = versioned ? pointerValue : journal
|
|
985
|
+
if (!UUID_RE.test(binding.transactionId || '')) throw new Error('journal transactionId is invalid')
|
|
986
|
+
exactKeys(binding.owner, ['host', 'pid'], 'upgrade recovery journal owner')
|
|
987
|
+
if (
|
|
988
|
+
typeof binding.owner.host !== 'string'
|
|
989
|
+
|| !binding.owner.host
|
|
990
|
+
|| !Number.isSafeInteger(binding.owner.pid)
|
|
991
|
+
|| binding.owner.pid <= 0
|
|
992
|
+
) throw new Error('upgrade recovery journal owner is invalid')
|
|
993
|
+
if (binding.owner.host !== hostname()) {
|
|
994
|
+
throw new Error(`transaction owner host cannot be proven inactive:${binding.owner.host}`)
|
|
995
|
+
}
|
|
996
|
+
if (processIsAlive(binding.owner.pid)) throw new Error(`upgrade transaction is still active on pid ${binding.owner.pid}`)
|
|
997
|
+
transactionRoot = validateTransactionRoot(binding.transactionRoot, binding.transactionId, binding.owner)
|
|
998
|
+
if (versioned) {
|
|
999
|
+
const leaseSource = stableRegularFile(
|
|
1000
|
+
join(transactionRoot, JOURNAL_LEASE_SOURCE),
|
|
1001
|
+
'upgrade recovery journal lease source',
|
|
1002
|
+
{ links: [2] },
|
|
1003
|
+
)
|
|
1004
|
+
if (
|
|
1005
|
+
journalInfo.nlink !== 2n
|
|
1006
|
+
|| !sameFileIdentity(leaseSource.info, journalInfo)
|
|
1007
|
+
|| !leaseSource.bytes.equals(pointer.bytes)
|
|
1008
|
+
) throw new Error('upgrade recovery journal lease differs from its capability source')
|
|
1009
|
+
journal = loadLatestJournalState(transactionRoot, pointerValue).journal
|
|
1010
|
+
} else {
|
|
1011
|
+
// Legacy schema-v5 journals remain recoverable across the lease protocol rollout. A two-link
|
|
1012
|
+
// crash state is accepted only when the exact capability-owned publication alias still exists.
|
|
1013
|
+
if (journalInfo.nlink === 2n) {
|
|
1014
|
+
const initialTemporary = join(transactionRoot, LEGACY_JOURNAL_PUBLISH)
|
|
1015
|
+
const temporary = stableRegularFile(initialTemporary, 'legacy upgrade journal publication alias', { links: [2] })
|
|
1016
|
+
if (!sameFileIdentity(temporary.info, journalInfo) || !temporary.bytes.equals(pointer.bytes)) {
|
|
1017
|
+
throw new Error('upgrade recovery journal has an unbound hard-link alias')
|
|
1018
|
+
}
|
|
1019
|
+
} else if (journalInfo.nlink !== 1n) {
|
|
1020
|
+
throw new Error('upgrade recovery journal has an unexpected hard-link alias')
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
exactKeys(journal, ['schemaVersion', 'repoRoot', 'transactionId', 'transactionRoot', 'owner', 'entries', 'nodeModules', 'providerSnapshot', 'commonSnapshot'], 'upgrade recovery journal')
|
|
1024
|
+
const modernJournal = journal.schemaVersion === 6
|
|
1025
|
+
const legacyJournal = journal.schemaVersion === 5
|
|
1026
|
+
if ((!modernJournal && !legacyJournal) || journal.repoRoot !== repoRoot) throw new Error('journal repository/schema binding mismatch')
|
|
1027
|
+
if (!UUID_RE.test(journal.transactionId || '')) throw new Error('journal transactionId is invalid')
|
|
1028
|
+
exactKeys(journal.owner, ['host', 'pid'], 'upgrade recovery journal owner')
|
|
1029
|
+
if (
|
|
1030
|
+
typeof journal.owner.host !== 'string'
|
|
1031
|
+
|| !journal.owner.host
|
|
1032
|
+
|| !Number.isSafeInteger(journal.owner.pid)
|
|
1033
|
+
|| journal.owner.pid <= 0
|
|
1034
|
+
) throw new Error('upgrade recovery journal owner is invalid')
|
|
1035
|
+
if (
|
|
1036
|
+
journal.transactionId !== binding.transactionId
|
|
1037
|
+
|| journal.transactionRoot !== transactionRoot
|
|
1038
|
+
|| JSON.stringify(journal.owner) !== JSON.stringify(binding.owner)
|
|
1039
|
+
) throw new Error('upgrade recovery journal state differs from its pointer binding')
|
|
1040
|
+
if (!Array.isArray(journal.entries) || journal.entries.length !== TRANSACTION_PATHS.length) {
|
|
1041
|
+
throw new Error('journal entries do not match the closed transaction allowlist')
|
|
1042
|
+
}
|
|
1043
|
+
const entries = journal.entries.map((entry, index) => {
|
|
1044
|
+
exactKeys(entry, ['path', 'source', 'backup', 'present', 'beforeFingerprint', 'afterRecorded', 'afterPresent', 'afterFingerprint', 'replacementStarted', 'replacementCompleted'], `journal entry ${index}`)
|
|
1045
|
+
const expectedPath = TRANSACTION_PATHS[index]
|
|
1046
|
+
const expectedSource = join(repoRoot, expectedPath)
|
|
1047
|
+
const expectedBackup = join(transactionRoot, String(index))
|
|
1048
|
+
if (
|
|
1049
|
+
entry.path !== expectedPath || entry.source !== expectedSource || entry.backup !== expectedBackup
|
|
1050
|
+
|| typeof entry.present !== 'boolean'
|
|
1051
|
+
) throw new Error(`journal entry ${index} is not the exact fixed transaction target`)
|
|
1052
|
+
validateStateRecord(entry, `journal entry ${index}`)
|
|
1053
|
+
assertNoSymlinkPath(repoRoot, dirname(expectedSource), `journal source parent:${expectedPath}`, { allowMissing: false })
|
|
1054
|
+
assertNoSymlinkPath(transactionRoot, expectedBackup, `journal backup:${expectedPath}`)
|
|
1055
|
+
if (entry.present) {
|
|
1056
|
+
if (!pathEntryExists(expectedBackup)) throw new Error(`missing recovery backup:${expectedPath}`)
|
|
1057
|
+
assertSafeTree(transactionRoot, expectedBackup, `journal backup tree:${expectedPath}`)
|
|
1058
|
+
if (pathFingerprint(expectedBackup, `journal backup:${expectedPath}`) !== entry.beforeFingerprint) {
|
|
1059
|
+
throw new Error(`recovery backup fingerprint mismatch:${expectedPath}`)
|
|
1060
|
+
}
|
|
1061
|
+
} else if (pathEntryExists(expectedBackup)) throw new Error(`unexpected backup for absent target:${expectedPath}`)
|
|
1062
|
+
return { ...entry }
|
|
1063
|
+
})
|
|
1064
|
+
const expectedModulesSource = join(repoRoot, 'node_modules')
|
|
1065
|
+
const expectedModulesBackup = join(transactionRoot, 'node_modules')
|
|
1066
|
+
if (modernJournal) validateNodeModulesStateRecord(journal.nodeModules, 'journal nodeModules')
|
|
1067
|
+
else exactKeys(journal.nodeModules, ['source', 'backup', 'present'], 'legacy journal nodeModules')
|
|
1068
|
+
if (
|
|
1069
|
+
journal.nodeModules.source !== expectedModulesSource
|
|
1070
|
+
|| journal.nodeModules.backup !== expectedModulesBackup
|
|
1071
|
+
|| typeof journal.nodeModules.present !== 'boolean'
|
|
1072
|
+
) throw new Error('invalid node_modules journal entry')
|
|
1073
|
+
assertNoSymlinkPath(repoRoot, dirname(expectedModulesSource), 'node_modules source parent', { allowMissing: false })
|
|
1074
|
+
assertNoSymlinkPath(transactionRoot, expectedModulesBackup, 'node_modules recovery backup')
|
|
1075
|
+
const modulesBackupPresent = pathEntryExists(expectedModulesBackup)
|
|
1076
|
+
if (modulesBackupPresent) {
|
|
1077
|
+
const info = lstatSync(expectedModulesBackup)
|
|
1078
|
+
if (!info.isDirectory() || info.isSymbolicLink()) throw new Error('node_modules recovery backup must be a real directory')
|
|
1079
|
+
} else if (journal.nodeModules.present) {
|
|
1080
|
+
assertNoSymlinkPath(repoRoot, expectedModulesSource, 'unmoved node_modules recovery source', { allowMissing: false })
|
|
1081
|
+
const info = lstatSync(expectedModulesSource)
|
|
1082
|
+
if (!info.isDirectory() || info.isSymbolicLink()) throw new Error('missing both safe node_modules recovery source and backup')
|
|
1083
|
+
}
|
|
1084
|
+
if (!journal.nodeModules.present && modulesBackupPresent) throw new Error('unexpected node_modules backup for absent original')
|
|
1085
|
+
if (modernJournal) {
|
|
1086
|
+
inspectNodeModulesJournalState(
|
|
1087
|
+
journal.nodeModules,
|
|
1088
|
+
join(transactionRoot, 'candidate-staging', 'node_modules'),
|
|
1089
|
+
'journal node_modules',
|
|
1090
|
+
)
|
|
1091
|
+
}
|
|
1092
|
+
let providerSnapshot = null
|
|
1093
|
+
let authenticatedIncomingCorpus = null
|
|
1094
|
+
if (journal.providerSnapshot !== null) {
|
|
1095
|
+
exactKeys(journal.providerSnapshot, ['entries', 'evidence', 'evidenceRoot', 'paths'], 'journal providerSnapshot')
|
|
1096
|
+
exactKeys(journal.providerSnapshot.evidence, [
|
|
1097
|
+
'consumerBomSha256', 'forkCorpusLockSha256', 'manifestSha256', 'providerLifecycleSha256', 'releaseVersion',
|
|
1098
|
+
], 'journal providerSnapshot evidence')
|
|
1099
|
+
const expectedEvidenceRoot = join(transactionRoot, 'provider-evidence')
|
|
1100
|
+
if (journal.providerSnapshot.evidenceRoot !== expectedEvidenceRoot) throw new Error('journal provider evidence root is not capability-bound')
|
|
1101
|
+
assertSafeTree(transactionRoot, expectedEvidenceRoot, 'journal provider evidence corpus')
|
|
1102
|
+
const installed = validateInstalledForkCorpus(expectedEvidenceRoot)
|
|
1103
|
+
authenticatedIncomingCorpus = installed
|
|
1104
|
+
if (JSON.stringify(journal.providerSnapshot.evidence) !== JSON.stringify(installed.evidence)) {
|
|
1105
|
+
throw new Error('journal provider snapshot is not bound to the installed manifest/corpus')
|
|
1106
|
+
}
|
|
1107
|
+
const expectedPaths = collectProviderMutationPaths(installed.manifest)
|
|
1108
|
+
if (JSON.stringify(journal.providerSnapshot.paths) !== JSON.stringify(expectedPaths)) {
|
|
1109
|
+
throw new Error('journal provider paths do not match the authenticated installed manifest')
|
|
1110
|
+
}
|
|
1111
|
+
if (!Array.isArray(journal.providerSnapshot.entries) || journal.providerSnapshot.entries.length !== expectedPaths.length) {
|
|
1112
|
+
throw new Error('journal provider entries do not match the authenticated provider path set')
|
|
1113
|
+
}
|
|
1114
|
+
const providerEntries = journal.providerSnapshot.entries.map((entry, index) => {
|
|
1115
|
+
exactKeys(entry, ['backup', 'path', 'present', 'source', 'beforeFingerprint', 'afterRecorded', 'afterPresent', 'afterFingerprint', 'replacementStarted', 'replacementCompleted'], `journal provider entry ${index}`)
|
|
1116
|
+
const expectedPath = expectedPaths[index]
|
|
1117
|
+
const expectedSource = join(repoRoot, expectedPath.path)
|
|
1118
|
+
const expectedBackup = join(transactionRoot, 'provider-staging', String(index))
|
|
1119
|
+
if (
|
|
1120
|
+
entry.path !== expectedPath.path || entry.source !== expectedSource || entry.backup !== expectedBackup
|
|
1121
|
+
|| typeof entry.present !== 'boolean'
|
|
1122
|
+
) throw new Error(`journal provider entry ${index} is not the authenticated provider target`)
|
|
1123
|
+
validateStateRecord(entry, `journal provider entry ${index}`)
|
|
1124
|
+
assertNoSymlinkPath(repoRoot, dirname(expectedSource), `journal provider source parent:${entry.path}`, { allowMissing: false })
|
|
1125
|
+
assertNoSymlinkPath(transactionRoot, expectedBackup, `journal provider backup:${entry.path}`)
|
|
1126
|
+
if (entry.present) {
|
|
1127
|
+
if (!pathEntryExists(expectedBackup)) throw new Error(`missing provider recovery backup:${entry.path}`)
|
|
1128
|
+
const info = lstatSync(expectedBackup)
|
|
1129
|
+
if ((expectedPath.kind === 'file' && !info.isFile()) || (expectedPath.kind === 'tree' && !info.isDirectory())) {
|
|
1130
|
+
throw new Error(`provider recovery backup kind mismatch:${entry.path}`)
|
|
1131
|
+
}
|
|
1132
|
+
assertSafeTree(transactionRoot, expectedBackup, `journal provider backup tree:${entry.path}`)
|
|
1133
|
+
if (pathFingerprint(expectedBackup, `journal provider backup:${entry.path}`) !== entry.beforeFingerprint) {
|
|
1134
|
+
throw new Error(`provider recovery backup fingerprint mismatch:${entry.path}`)
|
|
1135
|
+
}
|
|
1136
|
+
} else if (pathEntryExists(expectedBackup)) throw new Error(`unexpected provider backup for absent target:${entry.path}`)
|
|
1137
|
+
return { ...entry }
|
|
1138
|
+
})
|
|
1139
|
+
const providerStagingRoot = join(transactionRoot, 'provider-staging')
|
|
1140
|
+
const expectedProviderBackups = providerEntries
|
|
1141
|
+
.flatMap((entry, index) => entry.present ? [String(index)] : [])
|
|
1142
|
+
.sort()
|
|
1143
|
+
const actualProviderBackups = pathEntryExists(providerStagingRoot)
|
|
1144
|
+
? readdirSync(providerStagingRoot).sort()
|
|
1145
|
+
: []
|
|
1146
|
+
if (JSON.stringify(actualProviderBackups) !== JSON.stringify(expectedProviderBackups)) {
|
|
1147
|
+
throw new Error('journal provider-staging inventory is not closed')
|
|
1148
|
+
}
|
|
1149
|
+
providerSnapshot = { ...journal.providerSnapshot, entries: providerEntries }
|
|
1150
|
+
} else if (pathEntryExists(join(transactionRoot, 'provider-staging'))) {
|
|
1151
|
+
// A crash may occur after inert provider backups are staged but before the atomically replaced
|
|
1152
|
+
// journal binds them. They are never used for restore and disappear with the transaction root.
|
|
1153
|
+
assertSafeTree(transactionRoot, join(transactionRoot, 'provider-staging'), 'unbound provider staging tree')
|
|
1154
|
+
}
|
|
1155
|
+
if (journal.providerSnapshot === null && pathEntryExists(join(transactionRoot, 'provider-evidence'))) {
|
|
1156
|
+
assertSafeTree(transactionRoot, join(transactionRoot, 'provider-evidence'), 'unbound provider evidence tree')
|
|
1157
|
+
}
|
|
1158
|
+
let commonSnapshot = null
|
|
1159
|
+
if (journal.commonSnapshot !== null) {
|
|
1160
|
+
if (!authenticatedIncomingCorpus) throw new Error('journal common snapshot has no authenticated incoming corpus')
|
|
1161
|
+
exactKeys(journal.commonSnapshot, ['backup', 'path', 'present', 'source', 'beforeFingerprint', 'afterRecorded', 'afterPresent', 'afterFingerprint', 'replacementStarted', 'replacementCompleted'], 'journal commonSnapshot')
|
|
1162
|
+
const expectedPath = normalizeRepositoryRelative(
|
|
1163
|
+
authenticatedIncomingCorpus.manifest.consumer.commonInstruction.destination,
|
|
1164
|
+
'authenticated common instruction destination',
|
|
1165
|
+
)
|
|
1166
|
+
const coveredByFixedRoot = TRANSACTION_PATHS.some(root => expectedPath === root || expectedPath.startsWith(`${root}/`))
|
|
1167
|
+
if (coveredByFixedRoot) throw new Error('journal common snapshot duplicates a fixed transaction target')
|
|
1168
|
+
const expectedSource = join(repoRoot, expectedPath)
|
|
1169
|
+
const expectedBackup = join(transactionRoot, 'common-staging', '0')
|
|
1170
|
+
if (
|
|
1171
|
+
journal.commonSnapshot.path !== expectedPath
|
|
1172
|
+
|| journal.commonSnapshot.source !== expectedSource
|
|
1173
|
+
|| journal.commonSnapshot.backup !== expectedBackup
|
|
1174
|
+
|| typeof journal.commonSnapshot.present !== 'boolean'
|
|
1175
|
+
) throw new Error('journal common snapshot is not bound to the authenticated common instruction')
|
|
1176
|
+
validateStateRecord(journal.commonSnapshot, 'journal commonSnapshot')
|
|
1177
|
+
assertNoSymlinkPath(repoRoot, dirname(expectedSource), 'journal common source parent', { allowMissing: false })
|
|
1178
|
+
assertNoSymlinkPath(transactionRoot, expectedBackup, 'journal common backup')
|
|
1179
|
+
if (journal.commonSnapshot.present) {
|
|
1180
|
+
if (!pathEntryExists(expectedBackup)) throw new Error('missing common-instruction recovery backup')
|
|
1181
|
+
const info = lstatSync(expectedBackup)
|
|
1182
|
+
if (!info.isFile() || info.isSymbolicLink() || info.nlink !== 1) throw new Error('common-instruction recovery backup must be one regular unaliased file')
|
|
1183
|
+
if (pathFingerprint(expectedBackup, 'journal common backup') !== journal.commonSnapshot.beforeFingerprint) {
|
|
1184
|
+
throw new Error('common-instruction recovery backup fingerprint mismatch')
|
|
1185
|
+
}
|
|
1186
|
+
} else if (pathEntryExists(expectedBackup)) throw new Error('unexpected common-instruction backup for absent target')
|
|
1187
|
+
const commonStagingRoot = join(transactionRoot, 'common-staging')
|
|
1188
|
+
const expectedCommonBackups = journal.commonSnapshot.present ? ['0'] : []
|
|
1189
|
+
const actualCommonBackups = pathEntryExists(commonStagingRoot) ? readdirSync(commonStagingRoot).sort() : []
|
|
1190
|
+
if (JSON.stringify(actualCommonBackups) !== JSON.stringify(expectedCommonBackups)) {
|
|
1191
|
+
throw new Error('journal common-staging inventory is not closed')
|
|
1192
|
+
}
|
|
1193
|
+
commonSnapshot = { ...journal.commonSnapshot }
|
|
1194
|
+
} else {
|
|
1195
|
+
if (authenticatedIncomingCorpus) {
|
|
1196
|
+
const expectedPath = normalizeRepositoryRelative(
|
|
1197
|
+
authenticatedIncomingCorpus.manifest.consumer.commonInstruction.destination,
|
|
1198
|
+
'authenticated common instruction destination',
|
|
1199
|
+
)
|
|
1200
|
+
const coveredByFixedRoot = TRANSACTION_PATHS.some(root => expectedPath === root || expectedPath.startsWith(`${root}/`))
|
|
1201
|
+
if (!coveredByFixedRoot) throw new Error('journal omits the authenticated common-instruction transaction target')
|
|
1202
|
+
}
|
|
1203
|
+
if (pathEntryExists(join(transactionRoot, 'common-staging'))) {
|
|
1204
|
+
// Inert common backup staged before the second journal replacement is never recovery authority.
|
|
1205
|
+
assertSafeTree(transactionRoot, join(transactionRoot, 'common-staging'), 'unbound common-instruction staging tree')
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
const recoveryRoot = join(transactionRoot, 'recovery-staging')
|
|
1209
|
+
if (pathEntryExists(recoveryRoot)) {
|
|
1210
|
+
const recoveryInfo = lstatSync(recoveryRoot)
|
|
1211
|
+
if (!recoveryInfo.isDirectory() || recoveryInfo.isSymbolicLink() || realpathSync(recoveryRoot) !== recoveryRoot) {
|
|
1212
|
+
throw new Error('journal recovery-staging root is unsafe')
|
|
1213
|
+
}
|
|
1214
|
+
const categories = new Map([
|
|
1215
|
+
['fixed', entries],
|
|
1216
|
+
['provider', providerSnapshot?.entries || []],
|
|
1217
|
+
['common', commonSnapshot ? [commonSnapshot] : []],
|
|
1218
|
+
])
|
|
1219
|
+
for (const category of readdirSync(recoveryRoot)) {
|
|
1220
|
+
const categoryEntries = categories.get(category)
|
|
1221
|
+
if (!categoryEntries?.length) throw new Error(`journal recovery-staging has unknown category:${category}`)
|
|
1222
|
+
const categoryRoot = join(recoveryRoot, category)
|
|
1223
|
+
const categoryInfo = lstatSync(categoryRoot)
|
|
1224
|
+
if (!categoryInfo.isDirectory() || categoryInfo.isSymbolicLink() || realpathSync(categoryRoot) !== categoryRoot) {
|
|
1225
|
+
throw new Error(`journal recovery-staging category is unsafe:${category}`)
|
|
1226
|
+
}
|
|
1227
|
+
for (const indexName of readdirSync(categoryRoot)) {
|
|
1228
|
+
if (!/^(0|[1-9][0-9]*)$/.test(indexName)) {
|
|
1229
|
+
throw new Error(`journal recovery-staging has invalid index:${category}/${indexName}`)
|
|
1230
|
+
}
|
|
1231
|
+
const index = Number(indexName)
|
|
1232
|
+
const entry = categoryEntries[index]
|
|
1233
|
+
if (!entry) throw new Error(`journal recovery-staging has unbound index:${category}/${indexName}`)
|
|
1234
|
+
validateRecoveryArtifactSlot({
|
|
1235
|
+
entry,
|
|
1236
|
+
transactionRoot,
|
|
1237
|
+
category,
|
|
1238
|
+
index,
|
|
1239
|
+
label: `journal ${category} target:${entry.path}`,
|
|
1240
|
+
})
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
const liveStaging = join(transactionRoot, 'live-staging')
|
|
1245
|
+
if (pathEntryExists(liveStaging)) {
|
|
1246
|
+
const liveInfo = lstatSync(liveStaging)
|
|
1247
|
+
if (!liveInfo.isDirectory() || liveInfo.isSymbolicLink() || realpathSync(liveStaging) !== liveStaging) {
|
|
1248
|
+
throw new Error('journal live-staging root is unsafe')
|
|
1249
|
+
}
|
|
1250
|
+
const categories = new Map([
|
|
1251
|
+
['fixed', entries],
|
|
1252
|
+
['provider', providerSnapshot?.entries || []],
|
|
1253
|
+
['common', commonSnapshot ? [commonSnapshot] : []],
|
|
1254
|
+
])
|
|
1255
|
+
for (const category of readdirSync(liveStaging)) {
|
|
1256
|
+
const categoryEntries = categories.get(category)
|
|
1257
|
+
if (!categoryEntries?.length) throw new Error(`journal live-staging has unknown category:${category}`)
|
|
1258
|
+
const categoryRoot = join(liveStaging, category)
|
|
1259
|
+
const categoryInfo = lstatSync(categoryRoot)
|
|
1260
|
+
if (!categoryInfo.isDirectory() || categoryInfo.isSymbolicLink() || realpathSync(categoryRoot) !== categoryRoot) {
|
|
1261
|
+
throw new Error(`journal live-staging category is unsafe:${category}`)
|
|
1262
|
+
}
|
|
1263
|
+
for (const indexName of readdirSync(categoryRoot)) {
|
|
1264
|
+
if (!/^(0|[1-9][0-9]*)$/.test(indexName)) {
|
|
1265
|
+
throw new Error(`journal live-staging has invalid index:${category}/${indexName}`)
|
|
1266
|
+
}
|
|
1267
|
+
const entry = categoryEntries[Number(indexName)]
|
|
1268
|
+
if (!entry || !entry.present || !entry.replacementStarted) {
|
|
1269
|
+
throw new Error(`journal live-staging has unbound index:${category}/${indexName}`)
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
for (const inertRoot of ['candidate-staging', 'candidate-staging.tmp', 'live-staging']) {
|
|
1275
|
+
const absolute = join(transactionRoot, inertRoot)
|
|
1276
|
+
if (!pathEntryExists(absolute)) continue
|
|
1277
|
+
const info = lstatSync(absolute)
|
|
1278
|
+
if (!info.isDirectory() || info.isSymbolicLink() || realpathSync(absolute) !== absolute) {
|
|
1279
|
+
throw new Error(`journal inert transaction root is unsafe:${inertRoot}`)
|
|
1280
|
+
}
|
|
1281
|
+
// candidate-staging may contain npm's legitimate .bin symlinks. It is never recovery
|
|
1282
|
+
// authority; recovery only removes the capability-bound sibling root. The moved live tree is
|
|
1283
|
+
// authoritative and was already validated before mutation, so keep its stronger closed-tree check.
|
|
1284
|
+
if (inertRoot === 'live-staging') assertSafeTree(transactionRoot, absolute, `journal inert transaction tree:${inertRoot}`)
|
|
1285
|
+
}
|
|
1286
|
+
const pendingJournalPublication = join(transactionRoot, LEGACY_JOURNAL_PUBLISH)
|
|
1287
|
+
if (pathEntryExists(pendingJournalPublication)) {
|
|
1288
|
+
const info = lstatSync(pendingJournalPublication)
|
|
1289
|
+
if (!info.isFile() || info.isSymbolicLink() || info.nlink !== 1) {
|
|
1290
|
+
throw new Error('journal pending publication is not one inert regular file')
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
const allowedRootNames = new Set([
|
|
1294
|
+
OWNER_MARKER,
|
|
1295
|
+
...(pathEntryExists(pendingJournalPublication) ? [LEGACY_JOURNAL_PUBLISH] : []),
|
|
1296
|
+
...(versioned ? [JOURNAL_LEASE_SOURCE, JOURNAL_STATE_DIRECTORY] : []),
|
|
1297
|
+
...entries.flatMap((entry, index) => entry.present ? [String(index)] : []),
|
|
1298
|
+
...(modulesBackupPresent ? ['node_modules'] : []),
|
|
1299
|
+
...(pathEntryExists(join(transactionRoot, 'provider-staging')) ? ['provider-staging'] : []),
|
|
1300
|
+
...(pathEntryExists(join(transactionRoot, 'provider-evidence')) ? ['provider-evidence'] : []),
|
|
1301
|
+
...(pathEntryExists(join(transactionRoot, 'common-staging')) ? ['common-staging'] : []),
|
|
1302
|
+
...(pathEntryExists(join(transactionRoot, 'candidate-staging')) ? ['candidate-staging'] : []),
|
|
1303
|
+
...(pathEntryExists(join(transactionRoot, 'candidate-staging.tmp')) ? ['candidate-staging.tmp'] : []),
|
|
1304
|
+
...(pathEntryExists(join(transactionRoot, 'live-staging')) ? ['live-staging'] : []),
|
|
1305
|
+
...(pathEntryExists(recoveryRoot) ? ['recovery-staging'] : []),
|
|
1306
|
+
])
|
|
1307
|
+
for (const name of readdirSync(transactionRoot)) {
|
|
1308
|
+
if (!allowedRootNames.has(name)) throw new Error(`transaction root has unowned content:${name}`)
|
|
1309
|
+
}
|
|
1310
|
+
// Validate every live authoritative target before the first restore mutation. A crash-recovery
|
|
1311
|
+
// journal is not permission to erase edits made after the last committed transaction state.
|
|
1312
|
+
for (const [index, entry] of entries.entries()) {
|
|
1313
|
+
assertRecoverableLiveState(
|
|
1314
|
+
entry,
|
|
1315
|
+
`journal fixed target:${entry.path}`,
|
|
1316
|
+
join(transactionRoot, 'live-staging', 'fixed', String(index)),
|
|
1317
|
+
join(transactionRoot, 'candidate-staging', entry.path),
|
|
1318
|
+
join(transactionRoot, 'recovery-staging', 'fixed', String(index), 'restore'),
|
|
1319
|
+
join(transactionRoot, 'recovery-staging', 'fixed', String(index), 'moved'),
|
|
1320
|
+
)
|
|
1321
|
+
}
|
|
1322
|
+
for (const [index, entry] of (providerSnapshot?.entries || []).entries()) {
|
|
1323
|
+
assertRecoverableLiveState(
|
|
1324
|
+
entry,
|
|
1325
|
+
`journal provider target:${entry.path}`,
|
|
1326
|
+
join(transactionRoot, 'live-staging', 'provider', String(index)),
|
|
1327
|
+
join(transactionRoot, 'candidate-staging', entry.path),
|
|
1328
|
+
join(transactionRoot, 'recovery-staging', 'provider', String(index), 'restore'),
|
|
1329
|
+
join(transactionRoot, 'recovery-staging', 'provider', String(index), 'moved'),
|
|
1330
|
+
)
|
|
1331
|
+
}
|
|
1332
|
+
if (commonSnapshot) {
|
|
1333
|
+
assertRecoverableLiveState(
|
|
1334
|
+
commonSnapshot,
|
|
1335
|
+
'journal common-instruction target',
|
|
1336
|
+
join(transactionRoot, 'live-staging', 'common', '0'),
|
|
1337
|
+
join(transactionRoot, 'candidate-staging', commonSnapshot.path),
|
|
1338
|
+
join(transactionRoot, 'recovery-staging', 'common', '0', 'restore'),
|
|
1339
|
+
join(transactionRoot, 'recovery-staging', 'common', '0', 'moved'),
|
|
1340
|
+
)
|
|
1341
|
+
}
|
|
1342
|
+
return {
|
|
1343
|
+
journal,
|
|
1344
|
+
journalPointerIdentity: {
|
|
1345
|
+
bytes: pointer.bytes,
|
|
1346
|
+
info: pointer.info,
|
|
1347
|
+
kind: versioned ? JOURNAL_LEASE_KIND : 'legacy-schema-v5-journal',
|
|
1348
|
+
},
|
|
1349
|
+
transactionRoot,
|
|
1350
|
+
entries,
|
|
1351
|
+
providerSnapshot,
|
|
1352
|
+
commonSnapshot,
|
|
1353
|
+
modules: {
|
|
1354
|
+
entry: { ...journal.nodeModules },
|
|
1355
|
+
backupPresent: modulesBackupPresent,
|
|
1356
|
+
modern: modernJournal,
|
|
1357
|
+
},
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
let recoveredInterruptedTransaction = false
|
|
1361
|
+
const assertRecoveryConsumerRole = (entries) => {
|
|
1362
|
+
const governanceEntry = entries.find((entry) => entry.path === 'governance')
|
|
1363
|
+
if (!governanceEntry?.present) {
|
|
1364
|
+
throw new Error('upgrade recovery journal has no consumer governance before-image')
|
|
1365
|
+
}
|
|
1366
|
+
const manifestPath = join(governanceEntry.backup, 'provider-cli-toolchain.json')
|
|
1367
|
+
assertNoSymlinkPath(
|
|
1368
|
+
governanceEntry.backup,
|
|
1369
|
+
manifestPath,
|
|
1370
|
+
'upgrade recovery consumer provider CLI manifest',
|
|
1371
|
+
{ allowMissing: false },
|
|
1372
|
+
)
|
|
1373
|
+
const manifestFile = stableRegularFile(
|
|
1374
|
+
manifestPath,
|
|
1375
|
+
'upgrade recovery consumer provider CLI manifest',
|
|
1376
|
+
{ links: [1], mode: 0o644 },
|
|
1377
|
+
)
|
|
1378
|
+
let manifest
|
|
1379
|
+
try {
|
|
1380
|
+
manifest = JSON.parse(manifestFile.bytes.toString('utf8'))
|
|
1381
|
+
} catch {
|
|
1382
|
+
throw new Error('upgrade recovery consumer provider CLI manifest is not valid JSON')
|
|
1383
|
+
}
|
|
1384
|
+
if (
|
|
1385
|
+
manifest?.kind !== 'provider-cli-toolchain'
|
|
1386
|
+
|| !Number.isSafeInteger(manifest.schemaVersion)
|
|
1387
|
+
|| manifest.schemaVersion < 1
|
|
1388
|
+
|| manifest.packageLock?.consumerPath !== 'governance/provider-cli-toolchain.package-lock.json'
|
|
1389
|
+
) throw new Error('upgrade recovery journal before-image is not a consumer provider CLI authority mirror')
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
// A SIGKILL or abrupt process termination cannot run a finally block. Recover the exact, validated
|
|
1393
|
+
// sibling backup on the next invocation before reading package.json or checking worktree cleanliness.
|
|
1394
|
+
// This is a process-crash contract, not a physical power-loss/filesystem-durability claim.
|
|
1395
|
+
if (!authorityProviderManifestPresent && pathEntryExists(journalPath)) {
|
|
1396
|
+
try {
|
|
1397
|
+
const {
|
|
1398
|
+
transactionRoot,
|
|
1399
|
+
journalPointerIdentity,
|
|
1400
|
+
entries,
|
|
1401
|
+
providerSnapshot,
|
|
1402
|
+
commonSnapshot,
|
|
1403
|
+
modules,
|
|
1404
|
+
} = validateRecoveryJournal()
|
|
1405
|
+
// The live consumer manifest can be path-absent when SIGKILL lands between the two atomic
|
|
1406
|
+
// governance-directory renames. Authenticate the immutable before-image instead of mistaking
|
|
1407
|
+
// that controlled gap for a non-consumer root. The authority marker is outside transaction
|
|
1408
|
+
// scope and was rejected before this journal was inspected.
|
|
1409
|
+
assertRecoveryConsumerRole(entries)
|
|
1410
|
+
// No mutation occurs until the entire journal, capability marker, fixed allowlist, every
|
|
1411
|
+
// backup, and the transaction-root inventory have been validated.
|
|
1412
|
+
for (const [index, entry] of entries.entries()) {
|
|
1413
|
+
restoreEntryAtomically({ entry, transactionRoot, category: 'fixed', index, label: `restore fixed target:${entry.path}` })
|
|
1414
|
+
}
|
|
1415
|
+
for (const [index, entry] of (providerSnapshot?.entries || []).entries()) {
|
|
1416
|
+
restoreEntryAtomically({ entry, transactionRoot, category: 'provider', index, label: `restore provider target:${entry.path}` })
|
|
1417
|
+
}
|
|
1418
|
+
if (commonSnapshot) {
|
|
1419
|
+
restoreEntryAtomically({ entry: commonSnapshot, transactionRoot, category: 'common', index: 0, label: 'restore common-instruction target' })
|
|
1420
|
+
}
|
|
1421
|
+
if (modules.modern) {
|
|
1422
|
+
restoreNodeModulesFromJournal(
|
|
1423
|
+
modules.entry,
|
|
1424
|
+
join(transactionRoot, 'candidate-staging', 'node_modules'),
|
|
1425
|
+
'restore node_modules',
|
|
1426
|
+
)
|
|
1427
|
+
} else if (modules.entry.present) {
|
|
1428
|
+
// The journal is written before the same-filesystem rename. Abrupt process termination may
|
|
1429
|
+
// therefore leave either the original source or the backup; both are safe, unambiguous states.
|
|
1430
|
+
if (modules.backupPresent) {
|
|
1431
|
+
safeRemoveRepositoryTarget('node_modules')
|
|
1432
|
+
renameSync(modules.entry.backup, modules.entry.source)
|
|
1433
|
+
}
|
|
1434
|
+
} else {
|
|
1435
|
+
safeRemoveRepositoryTarget('node_modules')
|
|
1436
|
+
}
|
|
1437
|
+
// Re-read every path-visible predecessor/recovery image immediately before deleting the
|
|
1438
|
+
// capability root. A writer that changed a moved inode after the first validation must leave
|
|
1439
|
+
// this journal intact for manual reconciliation rather than lose those bytes.
|
|
1440
|
+
for (const [index, entry] of entries.entries()) {
|
|
1441
|
+
validateRecoveryArtifactSlot({ entry, transactionRoot, category: 'fixed', index, label: `final restore fixed target:${entry.path}` })
|
|
1442
|
+
assertRecoverableLiveState(
|
|
1443
|
+
entry,
|
|
1444
|
+
`final restore fixed target:${entry.path}`,
|
|
1445
|
+
join(transactionRoot, 'live-staging', 'fixed', String(index)),
|
|
1446
|
+
join(transactionRoot, 'candidate-staging', entry.path),
|
|
1447
|
+
join(transactionRoot, 'recovery-staging', 'fixed', String(index), 'restore'),
|
|
1448
|
+
join(transactionRoot, 'recovery-staging', 'fixed', String(index), 'moved'),
|
|
1449
|
+
)
|
|
1450
|
+
}
|
|
1451
|
+
for (const [index, entry] of (providerSnapshot?.entries || []).entries()) {
|
|
1452
|
+
validateRecoveryArtifactSlot({ entry, transactionRoot, category: 'provider', index, label: `final restore provider target:${entry.path}` })
|
|
1453
|
+
assertRecoverableLiveState(
|
|
1454
|
+
entry,
|
|
1455
|
+
`final restore provider target:${entry.path}`,
|
|
1456
|
+
join(transactionRoot, 'live-staging', 'provider', String(index)),
|
|
1457
|
+
join(transactionRoot, 'candidate-staging', entry.path),
|
|
1458
|
+
join(transactionRoot, 'recovery-staging', 'provider', String(index), 'restore'),
|
|
1459
|
+
join(transactionRoot, 'recovery-staging', 'provider', String(index), 'moved'),
|
|
1460
|
+
)
|
|
1461
|
+
}
|
|
1462
|
+
if (commonSnapshot) {
|
|
1463
|
+
validateRecoveryArtifactSlot({ entry: commonSnapshot, transactionRoot, category: 'common', index: 0, label: 'final restore common-instruction target' })
|
|
1464
|
+
assertRecoverableLiveState(
|
|
1465
|
+
commonSnapshot,
|
|
1466
|
+
'final restore common-instruction target',
|
|
1467
|
+
join(transactionRoot, 'live-staging', 'common', '0'),
|
|
1468
|
+
join(transactionRoot, 'candidate-staging', commonSnapshot.path),
|
|
1469
|
+
join(transactionRoot, 'recovery-staging', 'common', '0', 'restore'),
|
|
1470
|
+
join(transactionRoot, 'recovery-staging', 'common', '0', 'moved'),
|
|
1471
|
+
)
|
|
1472
|
+
}
|
|
1473
|
+
if (modules.modern) {
|
|
1474
|
+
const restored = inspectNodeModulesJournalState(
|
|
1475
|
+
modules.entry,
|
|
1476
|
+
join(transactionRoot, 'candidate-staging', 'node_modules'),
|
|
1477
|
+
'final restore node_modules',
|
|
1478
|
+
)
|
|
1479
|
+
if (
|
|
1480
|
+
modules.entry.present
|
|
1481
|
+
? !restored.exactBeforeState
|
|
1482
|
+
: restored.sourcePresent || restored.backupPresent
|
|
1483
|
+
) throw new Error('final restore node_modules differs from its exact predecessor')
|
|
1484
|
+
}
|
|
1485
|
+
releaseJournalPointer({
|
|
1486
|
+
expectedIdentity: journalPointerIdentity,
|
|
1487
|
+
transactionRoot,
|
|
1488
|
+
label: 'upgrade recovery journal pointer',
|
|
1489
|
+
})
|
|
1490
|
+
// Releasing the public pointer is the recovery commit point. Capability-root deletion is
|
|
1491
|
+
// post-commit garbage collection and must never turn a completed restore into an unjournaled
|
|
1492
|
+
// second rollback attempt.
|
|
1493
|
+
try {
|
|
1494
|
+
rmSync(transactionRoot, { recursive: true, force: true })
|
|
1495
|
+
} catch (error) {
|
|
1496
|
+
console.error(`GOV-UPGRADE-RECOVERY-GC-DEFERRED:${transactionRoot}:${error?.message || error}`)
|
|
1497
|
+
}
|
|
1498
|
+
recoveredInterruptedTransaction = true
|
|
1499
|
+
} catch (error) {
|
|
1500
|
+
console.error(`GOV-UPGRADE-RECOVERY-BLOCKED:${error?.message || error}`)
|
|
1501
|
+
process.exit(2)
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
const consumerProviderManifestPresent = pathEntryExists(join(repoRoot, PROVIDER_CLI_CONSUMER_MANIFEST))
|
|
1505
|
+
|
|
1506
|
+
const argv = process.argv.slice(2)
|
|
1507
|
+
const cliFailure = (message) => {
|
|
1508
|
+
const diagnostic = {
|
|
1509
|
+
ruleId: 'GOV-UPGRADE-CLI-001',
|
|
1510
|
+
severity: 'BLOCKER',
|
|
1511
|
+
message,
|
|
1512
|
+
}
|
|
1513
|
+
if (argv.filter((token) => token === '--json').length === 1) {
|
|
1514
|
+
process.stdout.write(JSON.stringify({
|
|
1515
|
+
ok: false,
|
|
1516
|
+
mode: 'invalid',
|
|
1517
|
+
mutationAuthorized: false,
|
|
1518
|
+
syntaxValid: false,
|
|
1519
|
+
targetVerification: 'not-started',
|
|
1520
|
+
targetVerified: false,
|
|
1521
|
+
ready: false,
|
|
1522
|
+
diagnostics: [diagnostic],
|
|
1523
|
+
}, null, 2) + '\n')
|
|
1524
|
+
} else console.error(`${diagnostic.ruleId}:${message}`)
|
|
1525
|
+
process.exit(1)
|
|
1526
|
+
}
|
|
1527
|
+
const testReleaseHandshakeAuthorized = process.env.NODE_ENV === 'test'
|
|
1528
|
+
const booleanOptions = new Set([
|
|
1529
|
+
'--apply',
|
|
1530
|
+
'--dry-run',
|
|
1531
|
+
'--json',
|
|
1532
|
+
...(testReleaseHandshakeAuthorized ? [TEST_RELEASE_HANDSHAKE_OPTION] : []),
|
|
1533
|
+
...(testReleaseHandshakeAuthorized ? [TEST_GC_FAILURE_OPTION] : []),
|
|
1534
|
+
...(testReleaseHandshakeAuthorized ? [TEST_NODE_MODULES_PUBLISH_HANDSHAKE_OPTION] : []),
|
|
1535
|
+
...(testReleaseHandshakeAuthorized ? [TEST_CONSUMER_ROLE_GAP_HANDSHAKE_OPTION] : []),
|
|
1536
|
+
])
|
|
1537
|
+
const valueOptions = new Set(['--to', '--design-system', '--storybook'])
|
|
1538
|
+
const parsedOptions = new Map()
|
|
1539
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
1540
|
+
const token = argv[index]
|
|
1541
|
+
if (!booleanOptions.has(token) && !valueOptions.has(token)) cliFailure(`unknown or positional option:${token}`)
|
|
1542
|
+
if (parsedOptions.has(token)) cliFailure(`duplicate option:${token}`)
|
|
1543
|
+
if (booleanOptions.has(token)) {
|
|
1544
|
+
parsedOptions.set(token, true)
|
|
1545
|
+
continue
|
|
1546
|
+
}
|
|
1547
|
+
const value = argv[index + 1]
|
|
1548
|
+
if (!value || value.startsWith('--')) cliFailure(`${token} requires one exact-version value`)
|
|
1549
|
+
parsedOptions.set(token, value)
|
|
1550
|
+
index += 1
|
|
1551
|
+
}
|
|
1552
|
+
if (parsedOptions.has('--apply') && parsedOptions.has('--dry-run')) cliFailure('--apply and --dry-run are mutually exclusive')
|
|
1553
|
+
if (
|
|
1554
|
+
parsedOptions.has(TEST_RELEASE_HANDSHAKE_OPTION)
|
|
1555
|
+
&& (!parsedOptions.has('--apply') || !parsedOptions.has('--json'))
|
|
1556
|
+
) cliFailure(`${TEST_RELEASE_HANDSHAKE_OPTION} requires --apply and --json`)
|
|
1557
|
+
if (
|
|
1558
|
+
parsedOptions.has(TEST_GC_FAILURE_OPTION)
|
|
1559
|
+
&& (!parsedOptions.has('--apply') || !parsedOptions.has('--json'))
|
|
1560
|
+
) cliFailure(`${TEST_GC_FAILURE_OPTION} requires --apply and --json`)
|
|
1561
|
+
if (
|
|
1562
|
+
parsedOptions.has(TEST_NODE_MODULES_PUBLISH_HANDSHAKE_OPTION)
|
|
1563
|
+
&& (!parsedOptions.has('--apply') || !parsedOptions.has('--json'))
|
|
1564
|
+
) cliFailure(`${TEST_NODE_MODULES_PUBLISH_HANDSHAKE_OPTION} requires --apply and --json`)
|
|
1565
|
+
if (
|
|
1566
|
+
parsedOptions.has(TEST_CONSUMER_ROLE_GAP_HANDSHAKE_OPTION)
|
|
1567
|
+
&& (!parsedOptions.has('--apply') || !parsedOptions.has('--json'))
|
|
1568
|
+
) cliFailure(`${TEST_CONSUMER_ROLE_GAP_HANDSHAKE_OPTION} requires --apply and --json`)
|
|
1569
|
+
if (parsedOptions.has('--to') && (parsedOptions.has('--design-system') || parsedOptions.has('--storybook'))) {
|
|
1570
|
+
cliFailure('--to cannot be combined with --design-system or --storybook')
|
|
1571
|
+
}
|
|
1572
|
+
if (parsedOptions.has('--design-system') !== parsedOptions.has('--storybook')) {
|
|
1573
|
+
cliFailure('--design-system and --storybook must be supplied together')
|
|
1574
|
+
}
|
|
1575
|
+
if (parsedOptions.has('--apply') && !parsedOptions.has('--to') && !parsedOptions.has('--design-system')) {
|
|
1576
|
+
cliFailure('--apply requires --to or both explicit package targets')
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
const APPLY = parsedOptions.has('--apply')
|
|
1580
|
+
const AS_JSON = parsedOptions.has('--json')
|
|
1581
|
+
const TEST_RELEASE_HANDSHAKE = parsedOptions.has(TEST_RELEASE_HANDSHAKE_OPTION)
|
|
1582
|
+
const TEST_GC_FAILURE = parsedOptions.has(TEST_GC_FAILURE_OPTION)
|
|
1583
|
+
const TEST_NODE_MODULES_PUBLISH_HANDSHAKE = parsedOptions.has(TEST_NODE_MODULES_PUBLISH_HANDSHAKE_OPTION)
|
|
1584
|
+
const TEST_CONSUMER_ROLE_GAP_HANDSHAKE = parsedOptions.has(TEST_CONSUMER_ROLE_GAP_HANDSHAKE_OPTION)
|
|
1585
|
+
const DRY_RUN = !APPLY
|
|
1586
|
+
const sharedTarget = parsedOptions.get('--to')
|
|
1587
|
+
const dsTarget = parsedOptions.get('--design-system') || sharedTarget
|
|
1588
|
+
const storybookTarget = parsedOptions.get('--storybook') || sharedTarget
|
|
1589
|
+
const exactVersion = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z][0-9A-Za-z.-]*)?(?:\+[0-9A-Za-z][0-9A-Za-z.-]*)?$/
|
|
1590
|
+
const parseVersion = (value) => {
|
|
1591
|
+
const match = String(value || '').match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/)
|
|
1592
|
+
if (!match) return null
|
|
1593
|
+
return { core: match.slice(1, 4).map(Number), pre: match[4]?.split('.') ?? null }
|
|
1594
|
+
}
|
|
1595
|
+
const compareVersion = (a, b) => {
|
|
1596
|
+
const left = parseVersion(a); const right = parseVersion(b)
|
|
1597
|
+
if (!left || !right) return null
|
|
1598
|
+
for (let i = 0; i < 3; i += 1) if (left.core[i] !== right.core[i]) return left.core[i] - right.core[i]
|
|
1599
|
+
if (left.pre === null || right.pre === null) return left.pre === right.pre ? 0 : left.pre === null ? 1 : -1
|
|
1600
|
+
for (let i = 0; i < Math.max(left.pre.length, right.pre.length); i += 1) {
|
|
1601
|
+
if (left.pre[i] === undefined || right.pre[i] === undefined) return left.pre[i] === undefined ? -1 : 1
|
|
1602
|
+
if (left.pre[i] === right.pre[i]) continue
|
|
1603
|
+
const ln = /^\d+$/.test(left.pre[i]); const rn = /^\d+$/.test(right.pre[i])
|
|
1604
|
+
if (ln && rn) return Number(left.pre[i]) - Number(right.pre[i])
|
|
1605
|
+
if (ln !== rn) return ln ? -1 : 1
|
|
1606
|
+
// SemVer prerelease identifiers are ASCII-only. UTF-8 byte order is therefore the exact
|
|
1607
|
+
// locale-independent ASCII lexical order required for non-numeric identifiers.
|
|
1608
|
+
return compareUtf8Bytes(left.pre[i], right.pre[i])
|
|
1609
|
+
}
|
|
1610
|
+
return 0
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
const project = JSON.parse(readFileSync('package.json', 'utf8'))
|
|
1614
|
+
const allDeps = { ...(project.dependencies || {}), ...(project.devDependencies || {}) }
|
|
1615
|
+
const current = {
|
|
1616
|
+
designSystem: allDeps['@qijenchen/design-system'] || null,
|
|
1617
|
+
storybook: allDeps['@qijenchen/storybook-config'] || null,
|
|
1618
|
+
}
|
|
1619
|
+
let currentLock = null
|
|
1620
|
+
try { currentLock = JSON.parse(readFileSync('package-lock.json', 'utf8')) } catch { /* checker reports malformed lock */ }
|
|
1621
|
+
const currentResolved = {
|
|
1622
|
+
designSystem: currentLock?.packages?.['node_modules/@qijenchen/design-system']?.version || null,
|
|
1623
|
+
storybook: currentLock?.packages?.['node_modules/@qijenchen/storybook-config']?.version || null,
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
const report = {
|
|
1627
|
+
ok: true,
|
|
1628
|
+
mode: DRY_RUN ? 'plan' : 'apply',
|
|
1629
|
+
mutationAuthorized: APPLY,
|
|
1630
|
+
syntaxValid: true,
|
|
1631
|
+
targetVerification: DRY_RUN ? 'deferred' : 'pending',
|
|
1632
|
+
targetVerified: false,
|
|
1633
|
+
ready: false,
|
|
1634
|
+
current,
|
|
1635
|
+
target: { designSystem: dsTarget || null, storybook: storybookTarget || null },
|
|
1636
|
+
refresh: null,
|
|
1637
|
+
provenance: null,
|
|
1638
|
+
toolchain: null,
|
|
1639
|
+
verification: null,
|
|
1640
|
+
providerCli: null,
|
|
1641
|
+
rollback: null,
|
|
1642
|
+
transactionGarbageCollection: null,
|
|
1643
|
+
diagnostics: [],
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
if (authorityProviderManifestPresent || !consumerProviderManifestPresent) {
|
|
1647
|
+
report.ok = false
|
|
1648
|
+
report.diagnostics.push({
|
|
1649
|
+
ruleId: 'GOV-UPGRADE-ROLE-001',
|
|
1650
|
+
severity: 'BLOCKER',
|
|
1651
|
+
message: authorityProviderManifestPresent
|
|
1652
|
+
? 'sync-all 是 consumer-only upgrade 入口;DS authority 必須使用受審查的 authority control-plane/update 流程。'
|
|
1653
|
+
: `sync-all consumer 缺少必要的 provider CLI authority mirror:${PROVIDER_CLI_CONSUMER_MANIFEST}`,
|
|
1654
|
+
})
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
if (recoveredInterruptedTransaction) {
|
|
1658
|
+
report.diagnostics.push({
|
|
1659
|
+
ruleId: 'GOV-UPGRADE-RECOVERY-001',
|
|
1660
|
+
severity: 'INFO',
|
|
1661
|
+
message: '已先從 process-crash recovery journal 完整還原前次中斷的 tracked snapshot 與 node_modules,再處理本次命令。',
|
|
1662
|
+
})
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
const emit = () => {
|
|
1666
|
+
if (AS_JSON) {
|
|
1667
|
+
process.stdout.write(JSON.stringify(report, null, 2) + '\n')
|
|
1668
|
+
return
|
|
1669
|
+
}
|
|
1670
|
+
console.log(`Governance upgrade ${report.mode.toUpperCase()}(mutable dist-tags disabled)`)
|
|
1671
|
+
console.log(` current: design-system=${current.designSystem || 'missing'} / storybook=${current.storybook || 'missing'}`)
|
|
1672
|
+
console.log(` target : design-system=${dsTarget || 'not selected'} / storybook=${storybookTarget || 'not selected'}`)
|
|
1673
|
+
console.log(` syntaxValid=${report.syntaxValid} / targetVerification=${report.targetVerification} / targetVerified=${report.targetVerified} / ready=${report.ready}`)
|
|
1674
|
+
for (const diagnostic of report.diagnostics) console.log(` ${diagnostic.severity}: ${diagnostic.message}`)
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
const transactionFailureRule = (error) => {
|
|
1678
|
+
const message = String(error?.message || error || '')
|
|
1679
|
+
if (
|
|
1680
|
+
error?.code === 'GOV-PROVIDER-CLI-CONTROL-PLANE-UPDATE-REQUIRED'
|
|
1681
|
+
|| message.includes('GOV-PROVIDER-CLI-CONTROL-PLANE-UPDATE-REQUIRED')
|
|
1682
|
+
) return 'GOV-UPGRADE-BOOTSTRAP-001'
|
|
1683
|
+
if (message.includes('GOV-UPGRADE-BOOTSTRAP-001')) return 'GOV-UPGRADE-BOOTSTRAP-001'
|
|
1684
|
+
if (message.includes('GOV-UPGRADE-BOOTSTRAP-002')) return 'GOV-UPGRADE-BOOTSTRAP-002'
|
|
1685
|
+
if (/audit(?:\s|[^a-z]).*(?:high|vulnerab)|(?:high|vulnerab).*audit/i.test(message)) return 'GOV-SUPPLY-005'
|
|
1686
|
+
if (/signature|attestation bundle|certificate verification/i.test(message)) return 'GOV-SUPPLY-002'
|
|
1687
|
+
if (/provenance|slsa|immutable release|release bom|release asset|source workflow|workflow mismatch|git commit|git tag/i.test(message)) return 'GOV-SUPPLY-003'
|
|
1688
|
+
if (/verified exact npm|npm (?:runtime|toolchain|tarball)|tar integrity|registry.*npm/i.test(message)) return 'GOV-SUPPLY-004'
|
|
1689
|
+
if (/npm (?:ci|install)|dependency install/i.test(message)) return 'GOV-UPGRADE-003'
|
|
1690
|
+
if (/corpus|provider lifecycle|materiali[sz]ation|mutation authority|governance lock|managed path|launcher destination/i.test(message)) return 'GOV-UPGRADE-008'
|
|
1691
|
+
return 'GOV-UPGRADE-007'
|
|
1692
|
+
}
|
|
1693
|
+
const transactionFailureMessage = (error) => {
|
|
1694
|
+
const message = String(error?.message || error || '')
|
|
1695
|
+
const ruleId = transactionFailureRule(error)
|
|
1696
|
+
if (
|
|
1697
|
+
(ruleId === 'GOV-UPGRADE-BOOTSTRAP-001' || ruleId === 'GOV-UPGRADE-BOOTSTRAP-002')
|
|
1698
|
+
&& !message.includes(REVIEWED_CONTROL_PLANE_UPDATE_ROUTE)
|
|
1699
|
+
) {
|
|
1700
|
+
return `${message}; ${REVIEWED_CONTROL_PLANE_UPDATE_ROUTE}`
|
|
1701
|
+
}
|
|
1702
|
+
return message
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
for (const [name, version] of [['design-system', dsTarget], ['storybook', storybookTarget]]) {
|
|
1706
|
+
if (version && !exactVersion.test(version)) {
|
|
1707
|
+
report.ok = false
|
|
1708
|
+
report.syntaxValid = false
|
|
1709
|
+
report.diagnostics.push({
|
|
1710
|
+
ruleId: 'GOV-VERSION-001',
|
|
1711
|
+
severity: 'BLOCKER',
|
|
1712
|
+
message: `${name} 必須是 exact semver;拒絕 dist-tag/range: ${version}`,
|
|
1713
|
+
})
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
if (dsTarget && storybookTarget && dsTarget !== storybookTarget) {
|
|
1717
|
+
report.ok = false
|
|
1718
|
+
report.syntaxValid = false
|
|
1719
|
+
report.diagnostics.push({
|
|
1720
|
+
ruleId: 'GOV-VERSION-002',
|
|
1721
|
+
severity: 'BLOCKER',
|
|
1722
|
+
message: `design-system / storybook-config 必須採同一不可變 release;收到 ${dsTarget} / ${storybookTarget}。`,
|
|
1723
|
+
})
|
|
1724
|
+
}
|
|
1725
|
+
for (const [name, target, installed] of [
|
|
1726
|
+
['design-system', dsTarget, currentResolved.designSystem],
|
|
1727
|
+
['storybook-config', storybookTarget, currentResolved.storybook],
|
|
1728
|
+
]) {
|
|
1729
|
+
if (target && installed && compareVersion(target, installed) < 0) {
|
|
1730
|
+
report.ok = false
|
|
1731
|
+
report.diagnostics.push({
|
|
1732
|
+
ruleId: 'GOV-VERSION-003',
|
|
1733
|
+
severity: 'BLOCKER',
|
|
1734
|
+
message: `${name} monotonic upgrade required;拒絕 ${installed} → ${target} downgrade。Rollback 使用 transaction,不靠發布舊版本。`,
|
|
1735
|
+
})
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
if (!dsTarget || !storybookTarget) {
|
|
1740
|
+
report.diagnostics.push({
|
|
1741
|
+
ruleId: 'GOV-UPGRADE-001',
|
|
1742
|
+
severity: 'INFO',
|
|
1743
|
+
message: '未選 target;本次只讀取現況,不查詢 registry、不修改 package/lock/generated adapters。',
|
|
1744
|
+
})
|
|
1745
|
+
emit()
|
|
1746
|
+
process.exit(report.ok ? 0 : 1)
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
if (DRY_RUN && report.syntaxValid) {
|
|
1750
|
+
report.ok = false
|
|
1751
|
+
report.diagnostics.push({
|
|
1752
|
+
ruleId: 'GOV-UPGRADE-VERIFICATION-001',
|
|
1753
|
+
severity: 'INFO',
|
|
1754
|
+
message: 'target syntax 已通過,但此 read-only plan 未查詢或驗證 immutable release、Release BOM、npm provenance 與簽章;targetVerification=deferred、ready=false。只有受保護的 --apply 流程才會先完成這些驗證,任一 target 不存在或證據無效時會在 live mutation 前 fail closed。',
|
|
1755
|
+
})
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
if (!report.ok || DRY_RUN) {
|
|
1759
|
+
emit()
|
|
1760
|
+
process.exit(1)
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
try {
|
|
1764
|
+
const governanceLockPath = join(repoRoot, 'governance/lock.json')
|
|
1765
|
+
assertNoSymlinkPath(repoRoot, governanceLockPath, 'current governance lock', { allowMissing: false })
|
|
1766
|
+
validateUpgradeTrustPolicy(JSON.parse(readFileSync(regularFile(governanceLockPath, 'current governance lock'), 'utf8')).upgradeTrust)
|
|
1767
|
+
} catch (error) {
|
|
1768
|
+
report.ok = false
|
|
1769
|
+
report.diagnostics.push({
|
|
1770
|
+
ruleId: 'GOV-SUPPLY-001',
|
|
1771
|
+
severity: 'BLOCKER',
|
|
1772
|
+
message: `目前受信任 governance lock 缺少 closed upgradeTrust policy:${error?.message || error}`,
|
|
1773
|
+
})
|
|
1774
|
+
emit()
|
|
1775
|
+
process.exit(1)
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
let git
|
|
1779
|
+
try {
|
|
1780
|
+
assertClosedGitLocalConfiguration(repoRoot)
|
|
1781
|
+
git = runClosedGit([
|
|
1782
|
+
'status',
|
|
1783
|
+
'--porcelain=v1',
|
|
1784
|
+
'-z',
|
|
1785
|
+
'--untracked-files=all',
|
|
1786
|
+
'--ignore-submodules=all',
|
|
1787
|
+
'--no-ahead-behind',
|
|
1788
|
+
'--no-renames',
|
|
1789
|
+
], { cwd: repoRoot })
|
|
1790
|
+
} catch (error) {
|
|
1791
|
+
git = { status: null, signal: null, error, stdout: '' }
|
|
1792
|
+
}
|
|
1793
|
+
if (git.error || git.signal !== null || git.status !== 0 || git.stdout.length !== 0) {
|
|
1794
|
+
report.ok = false
|
|
1795
|
+
report.diagnostics.push({
|
|
1796
|
+
ruleId: 'GOV-UPGRADE-002',
|
|
1797
|
+
severity: 'BLOCKER',
|
|
1798
|
+
message: `upgrade apply 只允許在封閉 Git 邊界驗證為乾淨的 working tree 執行;請先保全既有變更並使用專用 upgrade branch。${git.error ? ` (${git.error.message})` : ''}`,
|
|
1799
|
+
})
|
|
1800
|
+
emit()
|
|
1801
|
+
process.exit(1)
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
const branch = runClosedGit(['symbolic-ref', '--quiet', '--short', 'HEAD'], { cwd: repoRoot })
|
|
1805
|
+
const branchName = !branch.error && branch.signal === null && branch.status === 0 ? branch.stdout.trim() : ''
|
|
1806
|
+
if (!branchName || /^(?:main|master)$/.test(branchName)) {
|
|
1807
|
+
report.ok = false
|
|
1808
|
+
report.diagnostics.push({
|
|
1809
|
+
ruleId: 'GOV-UPGRADE-009',
|
|
1810
|
+
severity: 'BLOCKER',
|
|
1811
|
+
message: branchName
|
|
1812
|
+
? `upgrade apply 不得直接在 ${branchName} 執行;請建立專用 upgrade branch 後再試。`
|
|
1813
|
+
: 'upgrade apply 不得在 detached HEAD 或無法驗證的 branch 執行;請切換到專用 upgrade branch。',
|
|
1814
|
+
})
|
|
1815
|
+
emit()
|
|
1816
|
+
process.exit(1)
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
const protectedBaseProbe = runClosedGit(['rev-parse', 'HEAD'], { cwd: repoRoot })
|
|
1820
|
+
const protectedBaseSha = !protectedBaseProbe.error && protectedBaseProbe.signal === null && protectedBaseProbe.status === 0
|
|
1821
|
+
? protectedBaseProbe.stdout.trim()
|
|
1822
|
+
: ''
|
|
1823
|
+
if (!/^[a-f0-9]{40}(?:[a-f0-9]{24})?$/.test(protectedBaseSha)) {
|
|
1824
|
+
report.ok = false
|
|
1825
|
+
report.diagnostics.push({
|
|
1826
|
+
ruleId: 'GOV-UPGRADE-009',
|
|
1827
|
+
severity: 'BLOCKER',
|
|
1828
|
+
message: 'upgrade apply 無法綁定一個 exact protected-base commit。',
|
|
1829
|
+
})
|
|
1830
|
+
emit()
|
|
1831
|
+
process.exit(1)
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
try {
|
|
1835
|
+
validateInstalledForkCorpus(repoRoot)
|
|
1836
|
+
} catch (error) {
|
|
1837
|
+
report.ok = false
|
|
1838
|
+
report.diagnostics.push({
|
|
1839
|
+
ruleId: 'GOV-UPGRADE-008',
|
|
1840
|
+
severity: 'BLOCKER',
|
|
1841
|
+
message: `目前安裝的 provider corpus 無法作為 ordinary upgrade 的 immutable predecessor:${error?.message || error}`,
|
|
1842
|
+
})
|
|
1843
|
+
emit()
|
|
1844
|
+
process.exit(1)
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
// The host's ignored node_modules is never a toolchain trust root. The protected reconstruction
|
|
1848
|
+
// independently downloads, measures and extracts the exact npm tarball bound by the committed
|
|
1849
|
+
// package-lock, then uses only that disposable runtime for ci/install/signature/high-audit gates.
|
|
1850
|
+
|
|
1851
|
+
// Transaction boundary is a UUID capability-bound sibling of the repository, so node_modules can
|
|
1852
|
+
// be moved atomically on the same filesystem. Reject symlinks anywhere in every managed tree before
|
|
1853
|
+
// making even the first backup: a lexical in-repo path must never redirect a copy outside the repo.
|
|
1854
|
+
const transactionId = randomUUID()
|
|
1855
|
+
const transactionRoot = expectedTransactionRoot(transactionId)
|
|
1856
|
+
const transactionOwner = { host: hostname(), pid: process.pid }
|
|
1857
|
+
let transactionState
|
|
1858
|
+
let nodeModulesState
|
|
1859
|
+
let journal
|
|
1860
|
+
let journalLeaseIdentity = null
|
|
1861
|
+
let journalStateSequence = -1
|
|
1862
|
+
let journalStateSha256 = null
|
|
1863
|
+
try {
|
|
1864
|
+
assertNoIgnoredManagedContent(repoRoot, TRANSACTION_PATHS, 'live fixed transaction authority')
|
|
1865
|
+
for (const path of TRANSACTION_PATHS) {
|
|
1866
|
+
assertSafeTree(repoRoot, join(repoRoot, path), `transaction source:${path}`, { allowMissing: true })
|
|
1867
|
+
}
|
|
1868
|
+
const initialNodeModulesPresent = pathEntryExists(join(repoRoot, 'node_modules'))
|
|
1869
|
+
assertNoSymlinkPath(repoRoot, join(repoRoot, 'node_modules'), 'node_modules transaction source')
|
|
1870
|
+
if (initialNodeModulesPresent) {
|
|
1871
|
+
const info = lstatSync(join(repoRoot, 'node_modules'))
|
|
1872
|
+
if (!info.isDirectory() || info.isSymbolicLink()) throw new Error('node_modules transaction source must be a real directory')
|
|
1873
|
+
}
|
|
1874
|
+
mkdirSync(transactionRoot, { mode: 0o700 })
|
|
1875
|
+
writeFileSync(join(transactionRoot, OWNER_MARKER), JSON.stringify({
|
|
1876
|
+
schemaVersion: 2,
|
|
1877
|
+
repoRoot,
|
|
1878
|
+
transactionId,
|
|
1879
|
+
owner: transactionOwner,
|
|
1880
|
+
}, null, 2) + '\n', { flag: 'wx', mode: 0o600 })
|
|
1881
|
+
transactionState = TRANSACTION_PATHS.map((path, index) => {
|
|
1882
|
+
const source = join(repoRoot, path)
|
|
1883
|
+
const backup = join(transactionRoot, String(index))
|
|
1884
|
+
const present = pathEntryExists(source)
|
|
1885
|
+
assertNoSymlinkPath(transactionRoot, backup, `transaction backup:${path}`)
|
|
1886
|
+
if (present) cpSync(source, backup, { recursive: true })
|
|
1887
|
+
return attachBeforeState({ path, source, backup, present }, `fixed transaction target:${path}`)
|
|
1888
|
+
})
|
|
1889
|
+
// Repeat after every before-image is complete. An ignored file created during a recursive copy
|
|
1890
|
+
// must remain live and block the transaction; it must never become invisible backup content that
|
|
1891
|
+
// is later deleted by a whole-tree replacement.
|
|
1892
|
+
assertNoIgnoredManagedContent(repoRoot, TRANSACTION_PATHS, 'snapshotted live fixed transaction authority')
|
|
1893
|
+
nodeModulesState = {
|
|
1894
|
+
source: join(repoRoot, 'node_modules'),
|
|
1895
|
+
backup: join(transactionRoot, 'node_modules'),
|
|
1896
|
+
present: initialNodeModulesPresent,
|
|
1897
|
+
beforeFingerprint: initialNodeModulesPresent
|
|
1898
|
+
? nodeModulesFingerprint(join(repoRoot, 'node_modules'), 'initial node_modules before-image')
|
|
1899
|
+
: null,
|
|
1900
|
+
afterRecorded: false,
|
|
1901
|
+
afterPresent: false,
|
|
1902
|
+
afterFingerprint: null,
|
|
1903
|
+
replacementStarted: false,
|
|
1904
|
+
replacementCompleted: false,
|
|
1905
|
+
}
|
|
1906
|
+
assertNoSymlinkPath(transactionRoot, nodeModulesState.backup, 'node_modules transaction backup')
|
|
1907
|
+
journal = {
|
|
1908
|
+
schemaVersion: 6,
|
|
1909
|
+
repoRoot,
|
|
1910
|
+
transactionId,
|
|
1911
|
+
transactionRoot,
|
|
1912
|
+
owner: transactionOwner,
|
|
1913
|
+
entries: transactionState,
|
|
1914
|
+
nodeModules: nodeModulesState,
|
|
1915
|
+
providerSnapshot: null,
|
|
1916
|
+
commonSnapshot: null,
|
|
1917
|
+
}
|
|
1918
|
+
} catch (error) {
|
|
1919
|
+
rmSync(transactionRoot, { recursive: true, force: true })
|
|
1920
|
+
report.ok = false
|
|
1921
|
+
report.diagnostics.push({
|
|
1922
|
+
ruleId: 'GOV-UPGRADE-007',
|
|
1923
|
+
severity: 'BLOCKER',
|
|
1924
|
+
message: `無法建立 closed upgrade transaction:${error?.message || error}`,
|
|
1925
|
+
})
|
|
1926
|
+
emit()
|
|
1927
|
+
process.exit(1)
|
|
1928
|
+
}
|
|
1929
|
+
const journalLease = journalLeaseBody({ transactionRoot, transactionId, owner: transactionOwner })
|
|
1930
|
+
const persistJournal = ({ initial = false } = {}) => {
|
|
1931
|
+
if (initial !== (journalStateSequence === -1)) {
|
|
1932
|
+
throw new Error('upgrade journal initial/subsequent state transition is invalid')
|
|
1933
|
+
}
|
|
1934
|
+
if (!initial) assertPublishedJournalLease(journalLeaseIdentity)
|
|
1935
|
+
const nextSequence = journalStateSequence + 1
|
|
1936
|
+
const nextSha256 = publishJournalState({
|
|
1937
|
+
transactionRoot,
|
|
1938
|
+
journal,
|
|
1939
|
+
sequence: nextSequence,
|
|
1940
|
+
previousStateSha256: journalStateSha256,
|
|
1941
|
+
})
|
|
1942
|
+
journalStateSequence = nextSequence
|
|
1943
|
+
journalStateSha256 = nextSha256
|
|
1944
|
+
if (initial) journalLeaseIdentity = publishJournalLease(journalLease)
|
|
1945
|
+
else assertPublishedJournalLease(journalLeaseIdentity)
|
|
1946
|
+
}
|
|
1947
|
+
try {
|
|
1948
|
+
persistJournal({ initial: true })
|
|
1949
|
+
} catch (error) {
|
|
1950
|
+
if (!error?.preserveTransactionRoot) rmSync(transactionRoot, { recursive: true, force: true })
|
|
1951
|
+
report.ok = false
|
|
1952
|
+
report.diagnostics.push({
|
|
1953
|
+
ruleId: 'GOV-UPGRADE-010',
|
|
1954
|
+
severity: 'BLOCKER',
|
|
1955
|
+
message: `無法獨占取得 upgrade transaction;另一個 sync-all 可能仍在執行:${error?.message || error}`,
|
|
1956
|
+
})
|
|
1957
|
+
emit()
|
|
1958
|
+
process.exit(1)
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
let transactionClosed = false
|
|
1962
|
+
let liveMutationStarted = false
|
|
1963
|
+
let providerTransactionState = []
|
|
1964
|
+
let commonTransactionState = null
|
|
1965
|
+
let liveProviderCliCommitIdentity = null
|
|
1966
|
+
const assertTransactionDiscardablesKnown = () => {
|
|
1967
|
+
if (!liveMutationStarted) return
|
|
1968
|
+
inspectNodeModulesJournalState(
|
|
1969
|
+
nodeModulesState,
|
|
1970
|
+
join(transactionRoot, 'candidate-staging', 'node_modules'),
|
|
1971
|
+
'cleanup node_modules',
|
|
1972
|
+
)
|
|
1973
|
+
for (const [index, entry] of journal.entries.entries()) {
|
|
1974
|
+
validateRecoveryArtifactSlot({ entry, transactionRoot, category: 'fixed', index, label: `cleanup fixed target:${entry.path}` })
|
|
1975
|
+
assertRecoverableLiveState(
|
|
1976
|
+
entry,
|
|
1977
|
+
`cleanup fixed target:${entry.path}`,
|
|
1978
|
+
join(transactionRoot, 'live-staging', 'fixed', String(index)),
|
|
1979
|
+
join(transactionRoot, 'candidate-staging', entry.path),
|
|
1980
|
+
join(transactionRoot, 'recovery-staging', 'fixed', String(index), 'restore'),
|
|
1981
|
+
join(transactionRoot, 'recovery-staging', 'fixed', String(index), 'moved'),
|
|
1982
|
+
)
|
|
1983
|
+
}
|
|
1984
|
+
for (const [index, entry] of (journal.providerSnapshot?.entries || []).entries()) {
|
|
1985
|
+
validateRecoveryArtifactSlot({ entry, transactionRoot, category: 'provider', index, label: `cleanup provider target:${entry.path}` })
|
|
1986
|
+
assertRecoverableLiveState(
|
|
1987
|
+
entry,
|
|
1988
|
+
`cleanup provider target:${entry.path}`,
|
|
1989
|
+
join(transactionRoot, 'live-staging', 'provider', String(index)),
|
|
1990
|
+
join(transactionRoot, 'candidate-staging', entry.path),
|
|
1991
|
+
join(transactionRoot, 'recovery-staging', 'provider', String(index), 'restore'),
|
|
1992
|
+
join(transactionRoot, 'recovery-staging', 'provider', String(index), 'moved'),
|
|
1993
|
+
)
|
|
1994
|
+
}
|
|
1995
|
+
if (journal.commonSnapshot) {
|
|
1996
|
+
const entry = journal.commonSnapshot
|
|
1997
|
+
validateRecoveryArtifactSlot({ entry, transactionRoot, category: 'common', index: 0, label: 'cleanup common-instruction target' })
|
|
1998
|
+
assertRecoverableLiveState(
|
|
1999
|
+
entry,
|
|
2000
|
+
'cleanup common-instruction target',
|
|
2001
|
+
join(transactionRoot, 'live-staging', 'common', '0'),
|
|
2002
|
+
join(transactionRoot, 'candidate-staging', entry.path),
|
|
2003
|
+
join(transactionRoot, 'recovery-staging', 'common', '0', 'restore'),
|
|
2004
|
+
join(transactionRoot, 'recovery-staging', 'common', '0', 'moved'),
|
|
2005
|
+
)
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
const waitForTestAcknowledgment = ({ enabled, readyMarker, acknowledgmentName, label }) => {
|
|
2009
|
+
if (!enabled) return
|
|
2010
|
+
const acknowledgmentPath = join(transactionRoot, acknowledgmentName)
|
|
2011
|
+
process.stderr.write(`${readyMarker}\n`)
|
|
2012
|
+
const deadline = Date.now() + 10_000
|
|
2013
|
+
while (Date.now() < deadline) {
|
|
2014
|
+
if (pathEntryExists(acknowledgmentPath)) {
|
|
2015
|
+
const acknowledgment = stableRegularFile(
|
|
2016
|
+
acknowledgmentPath,
|
|
2017
|
+
`${label} acknowledgment`,
|
|
2018
|
+
{ links: [1] },
|
|
2019
|
+
)
|
|
2020
|
+
if (!acknowledgment.bytes.equals(Buffer.from('ACK\n'))) {
|
|
2021
|
+
throw new Error(`${label} acknowledgment is invalid`)
|
|
2022
|
+
}
|
|
2023
|
+
rmSync(acknowledgmentPath)
|
|
2024
|
+
return
|
|
2025
|
+
}
|
|
2026
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10)
|
|
2027
|
+
}
|
|
2028
|
+
throw new Error(`${label} acknowledgment timed out`)
|
|
2029
|
+
}
|
|
2030
|
+
const cleanupTransaction = ({ commit = false } = {}) => {
|
|
2031
|
+
// Revalidate every predecessor and recovery image immediately before discarding the capability
|
|
2032
|
+
// root. Path-visible concurrent edits therefore remain preserved behind the journal and fail
|
|
2033
|
+
// closed. Uncooperative writers that retain an old open fd are outside the pathname-CAS contract.
|
|
2034
|
+
assertTransactionDiscardablesKnown()
|
|
2035
|
+
// Capture whichever inode is currently public before discarding the capability root. Only the
|
|
2036
|
+
// exact immutable lease can authorize deletion; substituted bytes are moved, restored with
|
|
2037
|
+
// create-if-absent, and preserved behind this transaction for manual reconciliation.
|
|
2038
|
+
assertPublishedJournalLease(journalLeaseIdentity)
|
|
2039
|
+
waitForTestAcknowledgment({
|
|
2040
|
+
enabled: TEST_RELEASE_HANDSHAKE,
|
|
2041
|
+
readyMarker: 'GOV-UPGRADE-TEST-JOURNAL-RELEASE-READY',
|
|
2042
|
+
acknowledgmentName: TEST_RELEASE_ACK,
|
|
2043
|
+
label: 'test journal release',
|
|
2044
|
+
})
|
|
2045
|
+
if (commit) {
|
|
2046
|
+
assertNodeModulesCommitState(nodeModulesState, 'commit node_modules')
|
|
2047
|
+
if (
|
|
2048
|
+
typeof protectedProviderCliStaticVerifier !== 'function'
|
|
2049
|
+
|| !liveProviderCliCommitIdentity
|
|
2050
|
+
) throw new Error('commit lost its protected-base provider CLI verifier capability')
|
|
2051
|
+
const commitProviderCli = providerCliStaticReceipt(
|
|
2052
|
+
protectedProviderCliStaticVerifier({ root: repoRoot }),
|
|
2053
|
+
'commit-bound live provider CLI',
|
|
2054
|
+
)
|
|
2055
|
+
assertSameProviderCliIdentity(
|
|
2056
|
+
liveProviderCliCommitIdentity,
|
|
2057
|
+
commitProviderCli,
|
|
2058
|
+
'live-to-commit',
|
|
2059
|
+
)
|
|
2060
|
+
}
|
|
2061
|
+
releaseJournalPointer({
|
|
2062
|
+
expectedIdentity: { ...journalLeaseIdentity, kind: JOURNAL_LEASE_KIND },
|
|
2063
|
+
transactionRoot,
|
|
2064
|
+
label: 'owned upgrade journal lease',
|
|
2065
|
+
})
|
|
2066
|
+
// The successful no-replace pointer release is the commit point. From here onward rollback has
|
|
2067
|
+
// no authority: deleting this private capability root is best-effort garbage collection only.
|
|
2068
|
+
transactionClosed = true
|
|
2069
|
+
try {
|
|
2070
|
+
if (commit && TEST_GC_FAILURE) {
|
|
2071
|
+
throw new Error('test-only transaction garbage-collection failure')
|
|
2072
|
+
}
|
|
2073
|
+
rmSync(transactionRoot, { recursive: true, force: true })
|
|
2074
|
+
report.transactionGarbageCollection = { completed: true, retainedPath: null }
|
|
2075
|
+
} catch (error) {
|
|
2076
|
+
report.transactionGarbageCollection = { completed: false, retainedPath: transactionRoot }
|
|
2077
|
+
report.diagnostics.push({
|
|
2078
|
+
ruleId: 'GOV-UPGRADE-GC-001',
|
|
2079
|
+
severity: 'INFO',
|
|
2080
|
+
message: `transaction 已提交;private recovery root 延後清理且不得觸發 rollback:${transactionRoot}:${error?.message || error}`,
|
|
2081
|
+
})
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
const rollbackTransaction = (reason) => {
|
|
2085
|
+
if (transactionClosed) return
|
|
2086
|
+
if (!liveMutationStarted) {
|
|
2087
|
+
report.rollback = { restored: true, reason, derivedState: 'disposable reconstruction never mutated the live repository' }
|
|
2088
|
+
return
|
|
2089
|
+
}
|
|
2090
|
+
const fixedEntries = journal.entries
|
|
2091
|
+
const providerEntries = journal.providerSnapshot?.entries || []
|
|
2092
|
+
const commonEntry = journal.commonSnapshot
|
|
2093
|
+
// Validate the ignored installed tree before restoring any tracked path. Unknown bytes are never
|
|
2094
|
+
// removed under rollback authority; the immutable journal remains for manual reconciliation.
|
|
2095
|
+
inspectNodeModulesJournalState(
|
|
2096
|
+
nodeModulesState,
|
|
2097
|
+
join(transactionRoot, 'candidate-staging', 'node_modules'),
|
|
2098
|
+
'rollback node_modules',
|
|
2099
|
+
)
|
|
2100
|
+
// The owner may restore only states committed by the same closed journal. Concurrent unknown
|
|
2101
|
+
// edits are preserved and turn the transaction into an explicit manual-recovery blocker.
|
|
2102
|
+
for (const [index, entry] of fixedEntries.entries()) {
|
|
2103
|
+
assertRecoverableLiveState(
|
|
2104
|
+
entry,
|
|
2105
|
+
`rollback fixed target:${entry.path}`,
|
|
2106
|
+
join(liveStagingRoot, 'fixed', String(index)),
|
|
2107
|
+
join(transactionRoot, 'candidate-staging', entry.path),
|
|
2108
|
+
join(transactionRoot, 'recovery-staging', 'fixed', String(index), 'restore'),
|
|
2109
|
+
join(transactionRoot, 'recovery-staging', 'fixed', String(index), 'moved'),
|
|
2110
|
+
)
|
|
2111
|
+
}
|
|
2112
|
+
for (const [index, entry] of providerEntries.entries()) {
|
|
2113
|
+
assertRecoverableLiveState(
|
|
2114
|
+
entry,
|
|
2115
|
+
`rollback provider target:${entry.path}`,
|
|
2116
|
+
join(liveStagingRoot, 'provider', String(index)),
|
|
2117
|
+
join(transactionRoot, 'candidate-staging', entry.path),
|
|
2118
|
+
join(transactionRoot, 'recovery-staging', 'provider', String(index), 'restore'),
|
|
2119
|
+
join(transactionRoot, 'recovery-staging', 'provider', String(index), 'moved'),
|
|
2120
|
+
)
|
|
2121
|
+
}
|
|
2122
|
+
if (commonEntry) {
|
|
2123
|
+
assertRecoverableLiveState(
|
|
2124
|
+
commonEntry,
|
|
2125
|
+
'rollback common-instruction target',
|
|
2126
|
+
join(liveStagingRoot, 'common', '0'),
|
|
2127
|
+
join(transactionRoot, 'candidate-staging', commonEntry.path),
|
|
2128
|
+
join(transactionRoot, 'recovery-staging', 'common', '0', 'restore'),
|
|
2129
|
+
join(transactionRoot, 'recovery-staging', 'common', '0', 'moved'),
|
|
2130
|
+
)
|
|
2131
|
+
}
|
|
2132
|
+
for (const [index, entry] of fixedEntries.entries()) {
|
|
2133
|
+
restoreEntryAtomically({ entry, transactionRoot, category: 'fixed', index, label: `rollback fixed target:${entry.path}` })
|
|
2134
|
+
}
|
|
2135
|
+
for (const [index, entry] of providerEntries.entries()) {
|
|
2136
|
+
restoreEntryAtomically({ entry, transactionRoot, category: 'provider', index, label: `rollback provider target:${entry.path}` })
|
|
2137
|
+
}
|
|
2138
|
+
if (commonEntry) {
|
|
2139
|
+
restoreEntryAtomically({ entry: commonEntry, transactionRoot, category: 'common', index: 0, label: 'rollback common-instruction target' })
|
|
2140
|
+
}
|
|
2141
|
+
restoreNodeModulesFromJournal(
|
|
2142
|
+
nodeModulesState,
|
|
2143
|
+
join(transactionRoot, 'candidate-staging', 'node_modules'),
|
|
2144
|
+
'rollback node_modules',
|
|
2145
|
+
)
|
|
2146
|
+
report.rollback = { restored: true, reason, derivedState: 'original node_modules restored byte-for-byte by same-filesystem rename' }
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
const exitForSignal = { SIGINT: 130, SIGTERM: 143, SIGHUP: 129 }
|
|
2150
|
+
for (const signal of Object.keys(exitForSignal)) process.once(signal, () => {
|
|
2151
|
+
try { rollbackTransaction(`interrupted by ${signal}`); cleanupTransaction() }
|
|
2152
|
+
catch (error) { console.error(`GOV-UPGRADE-ROLLBACK-BLOCKED:${error?.message || error}`) }
|
|
2153
|
+
process.exit(exitForSignal[signal])
|
|
2154
|
+
})
|
|
2155
|
+
|
|
2156
|
+
const candidateRoot = join(transactionRoot, 'candidate-staging')
|
|
2157
|
+
const candidateTemporary = join(transactionRoot, 'candidate-staging.tmp')
|
|
2158
|
+
const liveStagingRoot = join(transactionRoot, 'live-staging')
|
|
2159
|
+
let reconstructedUpgrade = null
|
|
2160
|
+
let reconstructedContext = null
|
|
2161
|
+
let protectedProviderCliStaticVerifier = null
|
|
2162
|
+
|
|
2163
|
+
const copyClosedCandidatePath = (sandbox, relativePath) => {
|
|
2164
|
+
const normalized = normalizeRepositoryRelative(relativePath, 'candidate materialization path')
|
|
2165
|
+
const source = join(sandbox, normalized)
|
|
2166
|
+
const destination = join(candidateTemporary, normalized)
|
|
2167
|
+
assertNoSymlinkPath(sandbox, source, `candidate materialization source:${normalized}`)
|
|
2168
|
+
if (!pathEntryExists(source)) return
|
|
2169
|
+
assertSafeTree(sandbox, source, `candidate materialization source:${normalized}`)
|
|
2170
|
+
mkdirSync(dirname(destination), { recursive: true })
|
|
2171
|
+
cpSync(source, destination, { recursive: true })
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
const captureDisposableCandidate = async ({ sandbox, incomingCorpus, providerTransition, provenance, toolchain, authority, operations }) => {
|
|
2175
|
+
assertNoSymlinkPath(transactionRoot, candidateTemporary, 'candidate materialization temporary root')
|
|
2176
|
+
mkdirSync(candidateTemporary, { mode: 0o700 })
|
|
2177
|
+
for (const path of TRANSACTION_PATHS) copyClosedCandidatePath(sandbox, path)
|
|
2178
|
+
|
|
2179
|
+
const providerPaths = providerTransition.paths.map((record) => ({ ...record }))
|
|
2180
|
+
for (const record of providerPaths) {
|
|
2181
|
+
const covered = TRANSACTION_PATHS.some((root) => record.path === root || record.path.startsWith(`${root}/`))
|
|
2182
|
+
if (covered) throw new Error(`provider path overlaps fixed transaction authority:${record.path}`)
|
|
2183
|
+
copyClosedCandidatePath(sandbox, record.path)
|
|
2184
|
+
}
|
|
2185
|
+
const commonPath = normalizeRepositoryRelative(
|
|
2186
|
+
incomingCorpus.manifest.consumer.commonInstruction.destination,
|
|
2187
|
+
'candidate common instruction destination',
|
|
2188
|
+
)
|
|
2189
|
+
if (
|
|
2190
|
+
providerPaths.some((record) => commonPath === record.path || commonPath.startsWith(`${record.path}/`) || record.path.startsWith(`${commonPath}/`))
|
|
2191
|
+
) throw new Error(`common instruction overlaps provider transaction authority:${commonPath}`)
|
|
2192
|
+
const commonCovered = TRANSACTION_PATHS.some((root) => commonPath === root || commonPath.startsWith(`${root}/`))
|
|
2193
|
+
if (!commonCovered) copyClosedCandidatePath(sandbox, commonPath)
|
|
2194
|
+
|
|
2195
|
+
assertNoIgnoredManagedContent(
|
|
2196
|
+
sandbox,
|
|
2197
|
+
[...TRANSACTION_PATHS, ...providerPaths.map((record) => record.path), commonPath],
|
|
2198
|
+
'disposable candidate managed authority',
|
|
2199
|
+
)
|
|
2200
|
+
|
|
2201
|
+
// `reconstructExpectedUpgrade` created this private checkout from the exact protected-base index,
|
|
2202
|
+
// proved its tree equals protectedBaseSha, and rejected every scripts/** candidate operation before
|
|
2203
|
+
// invoking this callback. Load the privileged provider implementation only from that closed
|
|
2204
|
+
// checkout. A poisoned live worktree hidden behind skip-worktree/assume-unchanged is never imported.
|
|
2205
|
+
// 2026-08-12 GOV-UPGRADE-BOOTSTRAP-001 同族退役(理由同 verify-upgrade-evidence.mjs 主閘):
|
|
2206
|
+
// 「交易期間不執行 incoming 程式」的安全性質由下方「provider module 只從 protected-base
|
|
2207
|
+
// sandbox 載入」原樣保持;scripts/** 改動只以資料入 PR diff,合併後的未來 run 才會執行。
|
|
2208
|
+
if (operations.some((entry) => entry.path === 'scripts' || entry.path.startsWith('scripts/'))) {
|
|
2209
|
+
console.error('INFO: release-bound scripts/** update carried as data(GOV-UPGRADE-BOOTSTRAP-001 retired 2026-08-12); provider module still loads from protected base only')
|
|
2210
|
+
}
|
|
2211
|
+
const providerModulePath = join(sandbox, 'scripts/setup-provider-cli-toolchain.mjs')
|
|
2212
|
+
assertNoSymlinkPath(sandbox, providerModulePath, 'protected-base provider CLI module', { allowMissing: false })
|
|
2213
|
+
regularFile(providerModulePath, 'protected-base provider CLI module')
|
|
2214
|
+
const providerModuleUrl = pathToFileURL(providerModulePath)
|
|
2215
|
+
providerModuleUrl.searchParams.set('protectedBaseSha', protectedBaseSha)
|
|
2216
|
+
const providerModule = await import(providerModuleUrl.href)
|
|
2217
|
+
if (
|
|
2218
|
+
typeof providerModule.setupProviderCliToolchain !== 'function'
|
|
2219
|
+
|| typeof providerModule.verifyProviderCliToolchainStatic !== 'function'
|
|
2220
|
+
) throw new Error('protected-base provider CLI module has an incompatible static API')
|
|
2221
|
+
const {
|
|
2222
|
+
setupProviderCliToolchain: protectedSetupProviderCliToolchain,
|
|
2223
|
+
verifyProviderCliToolchainStatic: protectedVerifyProviderCliToolchainStatic,
|
|
2224
|
+
} = providerModule
|
|
2225
|
+
protectedProviderCliStaticVerifier = protectedVerifyProviderCliToolchainStatic
|
|
2226
|
+
|
|
2227
|
+
// Provider binaries are content-verified but deliberately not executed.
|
|
2228
|
+
const sandboxProviderCli = providerCliStaticReceipt(await protectedSetupProviderCliToolchain({
|
|
2229
|
+
root: sandbox,
|
|
2230
|
+
probeExecutables: false,
|
|
2231
|
+
}), 'disposable sandbox provider CLI setup', { setup: true })
|
|
2232
|
+
|
|
2233
|
+
const candidateModulesSource = join(sandbox, 'node_modules')
|
|
2234
|
+
if (!pathEntryExists(candidateModulesSource)) throw new Error('disposable reconstruction did not produce node_modules')
|
|
2235
|
+
const candidateModulesInfo = lstatSync(candidateModulesSource)
|
|
2236
|
+
if (!candidateModulesInfo.isDirectory() || candidateModulesInfo.isSymbolicLink()) {
|
|
2237
|
+
throw new Error('disposable reconstructed node_modules is not a real directory')
|
|
2238
|
+
}
|
|
2239
|
+
// npm's .bin links are relative to their containing node_modules. Node's default cp behavior
|
|
2240
|
+
// resolves and rewrites those links to the disposable sandbox, which is deleted immediately
|
|
2241
|
+
// after reconstruction. Preserve the link bytes verbatim so the atomic directory move keeps
|
|
2242
|
+
// every executable link relative and live.
|
|
2243
|
+
cpSync(candidateModulesSource, join(candidateTemporary, 'node_modules'), {
|
|
2244
|
+
recursive: true,
|
|
2245
|
+
verbatimSymlinks: true,
|
|
2246
|
+
})
|
|
2247
|
+
renameSync(candidateTemporary, candidateRoot)
|
|
2248
|
+
const candidateProviderCli = providerCliStaticReceipt(
|
|
2249
|
+
protectedVerifyProviderCliToolchainStatic({ root: candidateRoot }),
|
|
2250
|
+
'captured candidate provider CLI',
|
|
2251
|
+
)
|
|
2252
|
+
assertSameProviderCliIdentity(
|
|
2253
|
+
sandboxProviderCli,
|
|
2254
|
+
candidateProviderCli,
|
|
2255
|
+
'sandbox-to-candidate',
|
|
2256
|
+
)
|
|
2257
|
+
captureNodeModulesAfterState(
|
|
2258
|
+
nodeModulesState,
|
|
2259
|
+
join(candidateRoot, 'node_modules'),
|
|
2260
|
+
'captured candidate node_modules',
|
|
2261
|
+
)
|
|
2262
|
+
reconstructedContext = {
|
|
2263
|
+
incomingCorpusEvidence: { ...incomingCorpus.evidence },
|
|
2264
|
+
providerPaths,
|
|
2265
|
+
commonPath,
|
|
2266
|
+
provenance: structuredClone(provenance),
|
|
2267
|
+
toolchain: structuredClone(toolchain),
|
|
2268
|
+
authoritySha256: sha256(JSON.stringify(authority)),
|
|
2269
|
+
operations: operations.map((entry) => ({ ...entry })),
|
|
2270
|
+
providerCli: {
|
|
2271
|
+
identity: candidateProviderCli,
|
|
2272
|
+
sandboxStaticVerified: true,
|
|
2273
|
+
candidateStaticVerified: true,
|
|
2274
|
+
},
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
const replaceFromCandidate = (entry, candidatePath, movedPath, label) => {
|
|
2279
|
+
const candidatePresent = pathEntryExists(candidatePath)
|
|
2280
|
+
const candidateFingerprint = candidatePresent ? pathFingerprint(candidatePath, `${label} staged candidate`) : null
|
|
2281
|
+
if (
|
|
2282
|
+
!entry.afterRecorded
|
|
2283
|
+
|| candidatePresent !== entry.afterPresent
|
|
2284
|
+
|| candidateFingerprint !== entry.afterFingerprint
|
|
2285
|
+
|| !entry.replacementStarted
|
|
2286
|
+
|| entry.replacementCompleted
|
|
2287
|
+
) throw new Error(`${label} candidate no longer matches the write-ahead journal`)
|
|
2288
|
+
if (entry.present) {
|
|
2289
|
+
mkdirSync(dirname(movedPath), { recursive: true })
|
|
2290
|
+
assertNoSymlinkPath(transactionRoot, movedPath, `${label} moved live before-image`)
|
|
2291
|
+
renameSync(entry.source, movedPath)
|
|
2292
|
+
try {
|
|
2293
|
+
if (pathFingerprint(movedPath, `${label} atomically moved before-image`) !== entry.beforeFingerprint) {
|
|
2294
|
+
throw new Error(`${label} changed between its final CAS check and atomic rename`)
|
|
2295
|
+
}
|
|
2296
|
+
} catch (error) {
|
|
2297
|
+
// The candidate has not been published. Put the exact bytes we just moved back whenever the
|
|
2298
|
+
// source name is still free; if another writer claimed it, preserve both names and leave the
|
|
2299
|
+
// recovery journal for explicit inspection rather than overwriting either edit.
|
|
2300
|
+
if (!pathEntryExists(entry.source) && pathEntryExists(movedPath)) renameSync(movedPath, entry.source)
|
|
2301
|
+
throw error
|
|
2302
|
+
}
|
|
2303
|
+
if (entry.path === 'governance') {
|
|
2304
|
+
waitForTestAcknowledgment({
|
|
2305
|
+
enabled: TEST_CONSUMER_ROLE_GAP_HANDSHAKE,
|
|
2306
|
+
readyMarker: 'GOV-UPGRADE-TEST-CONSUMER-ROLE-GAP-READY',
|
|
2307
|
+
acknowledgmentName: TEST_CONSUMER_ROLE_GAP_ACK,
|
|
2308
|
+
label: 'test consumer role gap',
|
|
2309
|
+
})
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
if (entry.afterPresent) {
|
|
2313
|
+
mkdirSync(dirname(entry.source), { recursive: true })
|
|
2314
|
+
assertNoSymlinkPath(repoRoot, dirname(entry.source), `${label} destination parent`, { allowMissing: false })
|
|
2315
|
+
const candidateInfo = lstatSync(candidatePath)
|
|
2316
|
+
if (candidateInfo.isFile() && !candidateInfo.isSymbolicLink()) {
|
|
2317
|
+
// link(2) is an atomic create-if-absent publication on the same filesystem. It cannot erase
|
|
2318
|
+
// a file concurrently created after the last CAS check. The short two-link crash state is
|
|
2319
|
+
// explicitly recognized only when both inodes match inside this capability root.
|
|
2320
|
+
linkSync(candidatePath, entry.source)
|
|
2321
|
+
rmSync(candidatePath)
|
|
2322
|
+
} else {
|
|
2323
|
+
// POSIX directory rename refuses a non-empty or wrong-kind destination. Recheck immediately;
|
|
2324
|
+
// an externally created destination therefore fails closed instead of being pre-removed.
|
|
2325
|
+
if (pathEntryExists(entry.source)) throw new Error(`${label} destination appeared during transaction`)
|
|
2326
|
+
renameSync(candidatePath, entry.source)
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
|
|
2331
|
+
const replaceJournaledEntry = (entry, candidatePath, movedPath, label) => {
|
|
2332
|
+
assertBeforeLiveState(entry, label)
|
|
2333
|
+
entry.replacementStarted = true
|
|
2334
|
+
persistJournal()
|
|
2335
|
+
replaceFromCandidate(entry, candidatePath, movedPath, label)
|
|
2336
|
+
assertExactAfterImage(entry, label)
|
|
2337
|
+
entry.replacementCompleted = true
|
|
2338
|
+
persistJournal()
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
const assertExactAfterImage = (entry, label) => {
|
|
2342
|
+
const present = pathEntryExists(entry.source)
|
|
2343
|
+
const fingerprint = present ? pathFingerprint(entry.source, `${label} live after-image`) : null
|
|
2344
|
+
if (!entry.afterRecorded || present !== entry.afterPresent || fingerprint !== entry.afterFingerprint) {
|
|
2345
|
+
throw new Error(`${label} live after-image differs from disposable reconstruction`)
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
|
|
2349
|
+
try {
|
|
2350
|
+
// Build and authenticate the complete target in a disposable protected-base checkout. No
|
|
2351
|
+
// incoming script/checker runs in the live repository, and no live path changes until every
|
|
2352
|
+
// after-image and rollback backup is closed in the owner-bound journal.
|
|
2353
|
+
reconstructedUpgrade = await reconstructExpectedUpgrade({
|
|
2354
|
+
repository: repoRoot,
|
|
2355
|
+
expectedBaseSha: protectedBaseSha,
|
|
2356
|
+
expectedVersion: dsTarget,
|
|
2357
|
+
captureMaterialized: captureDisposableCandidate,
|
|
2358
|
+
})
|
|
2359
|
+
report.targetVerification = 'verified'
|
|
2360
|
+
report.targetVerified = true
|
|
2361
|
+
if (!reconstructedContext || !pathEntryExists(candidateRoot)) throw new Error('disposable reconstruction did not publish a candidate snapshot')
|
|
2362
|
+
if (typeof protectedProviderCliStaticVerifier !== 'function') {
|
|
2363
|
+
throw new Error('protected-base provider CLI verifier capability was not retained')
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2366
|
+
assertClosedGitLocalConfiguration(repoRoot)
|
|
2367
|
+
const freshHead = runClosedGit(['rev-parse', 'HEAD'], { cwd: repoRoot })
|
|
2368
|
+
const freshStatus = runClosedGit([
|
|
2369
|
+
'status',
|
|
2370
|
+
'--porcelain=v1',
|
|
2371
|
+
'-z',
|
|
2372
|
+
'--untracked-files=all',
|
|
2373
|
+
'--ignore-submodules=all',
|
|
2374
|
+
'--no-ahead-behind',
|
|
2375
|
+
'--no-renames',
|
|
2376
|
+
], { cwd: repoRoot, output: 'buffer' })
|
|
2377
|
+
if (
|
|
2378
|
+
freshHead.error
|
|
2379
|
+
|| freshHead.signal !== null
|
|
2380
|
+
|| freshHead.status !== 0
|
|
2381
|
+
|| freshHead.stdout.trim() !== protectedBaseSha
|
|
2382
|
+
|| freshStatus.error
|
|
2383
|
+
|| freshStatus.signal !== null
|
|
2384
|
+
|| freshStatus.status !== 0
|
|
2385
|
+
|| !Buffer.isBuffer(freshStatus.stdout)
|
|
2386
|
+
|| freshStatus.stdout.length !== 0
|
|
2387
|
+
) {
|
|
2388
|
+
throw new Error('protected base changed during disposable reconstruction')
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
const incomingForkCorpus = validateInstalledForkCorpus(candidateRoot)
|
|
2392
|
+
if (
|
|
2393
|
+
incomingForkCorpus.evidence.releaseVersion !== dsTarget
|
|
2394
|
+
|| JSON.stringify(incomingForkCorpus.evidence) !== JSON.stringify(reconstructedContext.incomingCorpusEvidence)
|
|
2395
|
+
) throw new Error('captured candidate corpus differs from protected-base reconstruction')
|
|
2396
|
+
const providerPaths = collectProviderMutationPaths(incomingForkCorpus.manifest)
|
|
2397
|
+
if (JSON.stringify(providerPaths) !== JSON.stringify(reconstructedContext.providerPaths)) {
|
|
2398
|
+
throw new Error('captured candidate provider path set differs from protected-base transition')
|
|
2399
|
+
}
|
|
2400
|
+
assertNoIgnoredManagedContent(
|
|
2401
|
+
repoRoot,
|
|
2402
|
+
[...providerPaths.map((record) => record.path), reconstructedContext.commonPath],
|
|
2403
|
+
'live provider/common transaction authority',
|
|
2404
|
+
)
|
|
2405
|
+
|
|
2406
|
+
for (const managedPath of Object.keys(incomingForkCorpus.manifest.consumer.managedFiles || {}).sort()) {
|
|
2407
|
+
const normalized = normalizeRepositoryRelative(managedPath, 'incoming managed path')
|
|
2408
|
+
if (!TRANSACTION_PATHS.some((root) => normalized === root || normalized.startsWith(`${root}/`))) {
|
|
2409
|
+
throw new Error(`incoming managed path is outside fixed transaction authority:${managedPath}`)
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
const launcherDestination = normalizeRepositoryRelative(
|
|
2413
|
+
incomingForkCorpus.manifest.consumer.launcherDestination,
|
|
2414
|
+
'incoming shared launcher destination',
|
|
2415
|
+
)
|
|
2416
|
+
if (!TRANSACTION_PATHS.some((root) => launcherDestination === root || launcherDestination.startsWith(`${root}/`))) {
|
|
2417
|
+
throw new Error(`incoming launcher destination is outside fixed transaction authority:${launcherDestination}`)
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2420
|
+
journal.entries = transactionState.map((entry) => captureCandidateState(
|
|
2421
|
+
entry,
|
|
2422
|
+
join(candidateRoot, entry.path),
|
|
2423
|
+
`fixed transaction target:${entry.path}`,
|
|
2424
|
+
))
|
|
2425
|
+
|
|
2426
|
+
const evidenceRoot = join(transactionRoot, 'provider-evidence')
|
|
2427
|
+
const candidateDesignSystem = join(candidateRoot, 'node_modules/@qijenchen/design-system')
|
|
2428
|
+
const evidenceDesignSystem = join(evidenceRoot, 'node_modules/@qijenchen/design-system')
|
|
2429
|
+
mkdirSync(evidenceDesignSystem, { recursive: true })
|
|
2430
|
+
for (const runtimePath of ['package.json', 'package-lock.json']) {
|
|
2431
|
+
cpSync(join(candidateRoot, runtimePath), join(evidenceRoot, runtimePath))
|
|
2432
|
+
}
|
|
2433
|
+
cpSync(join(candidateDesignSystem, 'package.json'), join(evidenceDesignSystem, 'package.json'))
|
|
2434
|
+
cpSync(join(candidateDesignSystem, 'ds-canonical/fork'), join(evidenceDesignSystem, 'ds-canonical/fork'), { recursive: true })
|
|
2435
|
+
mkdirSync(join(evidenceDesignSystem, 'src/tokens'), { recursive: true })
|
|
2436
|
+
cpSync(
|
|
2437
|
+
join(candidateDesignSystem, 'src/tokens/utility-registry.json'),
|
|
2438
|
+
join(evidenceDesignSystem, 'src/tokens/utility-registry.json'),
|
|
2439
|
+
)
|
|
2440
|
+
assertSafeTree(transactionRoot, evidenceRoot, 'captured provider evidence corpus')
|
|
2441
|
+
const evidenceCorpus = validateInstalledForkCorpus(evidenceRoot)
|
|
2442
|
+
if (JSON.stringify(evidenceCorpus.evidence) !== JSON.stringify(incomingForkCorpus.evidence)) {
|
|
2443
|
+
throw new Error('captured provider evidence differs from candidate corpus')
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
const providerStaging = join(transactionRoot, 'provider-staging')
|
|
2447
|
+
mkdirSync(providerStaging)
|
|
2448
|
+
providerTransactionState = providerPaths.map((record, index) => {
|
|
2449
|
+
const source = join(repoRoot, record.path)
|
|
2450
|
+
const backup = join(providerStaging, String(index))
|
|
2451
|
+
const present = pathEntryExists(source)
|
|
2452
|
+
assertNoSymlinkPath(repoRoot, source, `provider transaction source:${record.path}`)
|
|
2453
|
+
if (present) {
|
|
2454
|
+
const info = lstatSync(source)
|
|
2455
|
+
if ((record.kind === 'file' && !info.isFile()) || (record.kind === 'tree' && !info.isDirectory())) {
|
|
2456
|
+
throw new Error(`provider transaction source kind mismatch:${record.path}:${record.kind}`)
|
|
2457
|
+
}
|
|
2458
|
+
assertSafeTree(repoRoot, source, `provider transaction source:${record.path}`)
|
|
2459
|
+
cpSync(source, backup, { recursive: true })
|
|
2460
|
+
}
|
|
2461
|
+
return captureCandidateState(
|
|
2462
|
+
attachBeforeState({ path: record.path, source, backup, present }, `provider transaction target:${record.path}`),
|
|
2463
|
+
join(candidateRoot, record.path),
|
|
2464
|
+
`provider transaction target:${record.path}`,
|
|
2465
|
+
)
|
|
2466
|
+
})
|
|
2467
|
+
journal.providerSnapshot = {
|
|
2468
|
+
evidence: { ...incomingForkCorpus.evidence },
|
|
2469
|
+
evidenceRoot,
|
|
2470
|
+
paths: providerPaths.map((record) => ({ ...record })),
|
|
2471
|
+
entries: providerTransactionState.map((entry) => ({ ...entry })),
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
const commonPath = reconstructedContext.commonPath
|
|
2475
|
+
const commonCovered = TRANSACTION_PATHS.some((root) => commonPath === root || commonPath.startsWith(`${root}/`))
|
|
2476
|
+
if (!commonCovered) {
|
|
2477
|
+
const commonSource = join(repoRoot, commonPath)
|
|
2478
|
+
const commonBackup = join(transactionRoot, 'common-staging', '0')
|
|
2479
|
+
mkdirSync(dirname(commonBackup), { recursive: true })
|
|
2480
|
+
const present = pathEntryExists(commonSource)
|
|
2481
|
+
assertNoSymlinkPath(repoRoot, commonSource, 'common-instruction transaction source')
|
|
2482
|
+
if (present) {
|
|
2483
|
+
const info = lstatSync(commonSource)
|
|
2484
|
+
if (!info.isFile() || info.isSymbolicLink() || info.nlink !== 1) {
|
|
2485
|
+
throw new Error('common-instruction transaction source must be one regular unaliased file')
|
|
2486
|
+
}
|
|
2487
|
+
cpSync(commonSource, commonBackup)
|
|
2488
|
+
}
|
|
2489
|
+
commonTransactionState = captureCandidateState(
|
|
2490
|
+
attachBeforeState({ path: commonPath, source: commonSource, backup: commonBackup, present }, `common transaction target:${commonPath}`),
|
|
2491
|
+
join(candidateRoot, commonPath),
|
|
2492
|
+
`common transaction target:${commonPath}`,
|
|
2493
|
+
)
|
|
2494
|
+
journal.commonSnapshot = { ...commonTransactionState }
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
// The second live scan closes the reconstruction/backup window for every dynamic provider and
|
|
2498
|
+
// common path. Any later path-visible edit is caught by the per-entry fingerprint CAS.
|
|
2499
|
+
assertNoIgnoredManagedContent(
|
|
2500
|
+
repoRoot,
|
|
2501
|
+
[...TRANSACTION_PATHS, ...providerPaths.map((record) => record.path), commonPath],
|
|
2502
|
+
'fully snapshotted live transaction authority',
|
|
2503
|
+
)
|
|
2504
|
+
|
|
2505
|
+
// Publish all exact before/after states and replacement intent before the first live rename.
|
|
2506
|
+
persistJournal()
|
|
2507
|
+
liveMutationStarted = true
|
|
2508
|
+
mkdirSync(liveStagingRoot)
|
|
2509
|
+
for (const [index, entry] of journal.entries.entries()) {
|
|
2510
|
+
replaceJournaledEntry(entry, join(candidateRoot, entry.path), join(liveStagingRoot, 'fixed', String(index)), `fixed target:${entry.path}`)
|
|
2511
|
+
}
|
|
2512
|
+
for (const [index, entry] of journal.providerSnapshot.entries.entries()) {
|
|
2513
|
+
replaceJournaledEntry(entry, join(candidateRoot, entry.path), join(liveStagingRoot, 'provider', String(index)), `provider target:${entry.path}`)
|
|
2514
|
+
}
|
|
2515
|
+
if (journal.commonSnapshot) {
|
|
2516
|
+
replaceJournaledEntry(journal.commonSnapshot, join(candidateRoot, journal.commonSnapshot.path), join(liveStagingRoot, 'common', '0'), 'common-instruction target')
|
|
2517
|
+
}
|
|
2518
|
+
const candidateModules = join(candidateRoot, 'node_modules')
|
|
2519
|
+
waitForTestAcknowledgment({
|
|
2520
|
+
enabled: TEST_NODE_MODULES_PUBLISH_HANDSHAKE,
|
|
2521
|
+
readyMarker: 'GOV-UPGRADE-TEST-NODE-MODULES-PUBLISH-READY',
|
|
2522
|
+
acknowledgmentName: TEST_NODE_MODULES_PUBLISH_ACK,
|
|
2523
|
+
label: 'test node_modules publication',
|
|
2524
|
+
})
|
|
2525
|
+
assertNodeModulesBeforeState(
|
|
2526
|
+
nodeModulesState,
|
|
2527
|
+
candidateModules,
|
|
2528
|
+
'node_modules publication',
|
|
2529
|
+
)
|
|
2530
|
+
nodeModulesState.replacementStarted = true
|
|
2531
|
+
persistJournal()
|
|
2532
|
+
if (nodeModulesState.present) renameSync(nodeModulesState.source, nodeModulesState.backup)
|
|
2533
|
+
if (!pathEntryExists(candidateModules)) throw new Error('candidate node_modules disappeared before replacement')
|
|
2534
|
+
renameSync(candidateModules, nodeModulesState.source)
|
|
2535
|
+
if (
|
|
2536
|
+
nodeModulesFingerprint(nodeModulesState.source, 'published node_modules after-image')
|
|
2537
|
+
!== nodeModulesState.afterFingerprint
|
|
2538
|
+
) throw new Error('published node_modules differs from the journaled candidate after-image')
|
|
2539
|
+
nodeModulesState.replacementCompleted = true
|
|
2540
|
+
persistJournal()
|
|
2541
|
+
const liveProviderCli = providerCliStaticReceipt(
|
|
2542
|
+
protectedProviderCliStaticVerifier({ root: repoRoot }),
|
|
2543
|
+
'live upgraded provider CLI',
|
|
2544
|
+
)
|
|
2545
|
+
assertSameProviderCliIdentity(
|
|
2546
|
+
reconstructedContext.providerCli.identity,
|
|
2547
|
+
liveProviderCli,
|
|
2548
|
+
'candidate-to-live',
|
|
2549
|
+
)
|
|
2550
|
+
liveProviderCliCommitIdentity = liveProviderCli
|
|
2551
|
+
|
|
2552
|
+
for (const entry of journal.entries) assertExactAfterImage(entry, `fixed target:${entry.path}`)
|
|
2553
|
+
for (const entry of journal.providerSnapshot.entries) assertExactAfterImage(entry, `provider target:${entry.path}`)
|
|
2554
|
+
if (journal.commonSnapshot) assertExactAfterImage(journal.commonSnapshot, 'common-instruction target')
|
|
2555
|
+
assertNoIgnoredManagedContent(
|
|
2556
|
+
repoRoot,
|
|
2557
|
+
[...TRANSACTION_PATHS, ...providerPaths.map((record) => record.path), commonPath],
|
|
2558
|
+
'live upgraded transaction authority',
|
|
2559
|
+
)
|
|
2560
|
+
assertClosedGitLocalConfiguration(repoRoot)
|
|
2561
|
+
const livePatch = runClosedGit(
|
|
2562
|
+
['diff', '--binary', '--full-index', '--no-ext-diff', '--no-textconv', '--no-renames'],
|
|
2563
|
+
{ cwd: repoRoot, output: 'buffer', maxOutputBytes: 64 * 1024 * 1024 },
|
|
2564
|
+
)
|
|
2565
|
+
const stagedDiff = runClosedGit(
|
|
2566
|
+
['diff', '--cached', '--name-only', '-z', '--no-ext-diff', '--no-textconv'],
|
|
2567
|
+
{ cwd: repoRoot, output: 'buffer' },
|
|
2568
|
+
)
|
|
2569
|
+
const endingHead = runClosedGit(['rev-parse', 'HEAD'], { cwd: repoRoot })
|
|
2570
|
+
const endingBranch = runClosedGit(['symbolic-ref', '--quiet', '--short', 'HEAD'], { cwd: repoRoot })
|
|
2571
|
+
if (
|
|
2572
|
+
livePatch.error
|
|
2573
|
+
|| livePatch.signal !== null
|
|
2574
|
+
|| livePatch.status !== 0
|
|
2575
|
+
|| !Buffer.isBuffer(livePatch.stdout)
|
|
2576
|
+
|| !livePatch.stdout.equals(reconstructedUpgrade.patchBytes)
|
|
2577
|
+
|| stagedDiff.error
|
|
2578
|
+
|| stagedDiff.signal !== null
|
|
2579
|
+
|| stagedDiff.status !== 0
|
|
2580
|
+
|| !Buffer.isBuffer(stagedDiff.stdout)
|
|
2581
|
+
|| stagedDiff.stdout.length !== 0
|
|
2582
|
+
|| endingHead.error
|
|
2583
|
+
|| endingHead.signal !== null
|
|
2584
|
+
|| endingHead.status !== 0
|
|
2585
|
+
|| endingHead.stdout.trim() !== protectedBaseSha
|
|
2586
|
+
|| endingBranch.error
|
|
2587
|
+
|| endingBranch.signal !== null
|
|
2588
|
+
|| endingBranch.status !== 0
|
|
2589
|
+
|| endingBranch.stdout.trim() !== branchName
|
|
2590
|
+
) throw new Error('live upgrade patch/index/base differs from protected-base reconstruction')
|
|
2591
|
+
|
|
2592
|
+
report.provenance = structuredClone(reconstructedUpgrade.authority.provenance)
|
|
2593
|
+
report.toolchain = structuredClone(reconstructedUpgrade.authority.toolchain)
|
|
2594
|
+
report.refresh = {
|
|
2595
|
+
changed: reconstructedUpgrade.operations.map((entry) => entry.path),
|
|
2596
|
+
mode: 'protected-base-disposable-reconstruction',
|
|
2597
|
+
}
|
|
2598
|
+
report.verification = {
|
|
2599
|
+
ok: true,
|
|
2600
|
+
mode: 'protected-base-disposable-reconstruction',
|
|
2601
|
+
attestationDigest: reconstructedUpgrade.authoritySha256,
|
|
2602
|
+
patchSha256: sha256(reconstructedUpgrade.patchBytes),
|
|
2603
|
+
treeSha256: reconstructedUpgrade.treeSha256,
|
|
2604
|
+
candidateCodeExecutedInWriter: false,
|
|
2605
|
+
providerCliExecutableRunsInWriter: false,
|
|
2606
|
+
}
|
|
2607
|
+
report.providerCli = {
|
|
2608
|
+
...liveProviderCli,
|
|
2609
|
+
sandboxStaticVerified: reconstructedContext.providerCli.sandboxStaticVerified,
|
|
2610
|
+
candidateStaticVerified: reconstructedContext.providerCli.candidateStaticVerified,
|
|
2611
|
+
liveStaticVerified: true,
|
|
2612
|
+
}
|
|
2613
|
+
report.diagnostics.push({
|
|
2614
|
+
ruleId: 'GOV-UPGRADE-005',
|
|
2615
|
+
severity: 'INFO',
|
|
2616
|
+
message: 'exact release 已由 protected-base 在 disposable checkout 重建、驗證並以已綁定 after-image 套用;請檢視 diff 後透過受保護 PR 合併。',
|
|
2617
|
+
})
|
|
2618
|
+
cleanupTransaction({ commit: true })
|
|
2619
|
+
report.ready = true
|
|
2620
|
+
emit()
|
|
2621
|
+
process.exit(0)
|
|
2622
|
+
|
|
2623
|
+
} catch (error) {
|
|
2624
|
+
report.ok = false
|
|
2625
|
+
report.ready = false
|
|
2626
|
+
report.diagnostics.push({
|
|
2627
|
+
ruleId: transactionFailureRule(error),
|
|
2628
|
+
severity: 'BLOCKER',
|
|
2629
|
+
message: `transaction exception:${transactionFailureMessage(error)}`,
|
|
2630
|
+
})
|
|
2631
|
+
try {
|
|
2632
|
+
rollbackTransaction('unexpected transaction exception')
|
|
2633
|
+
cleanupTransaction()
|
|
2634
|
+
} catch (rollbackError) {
|
|
2635
|
+
report.rollback = { restored: false, reason: `${rollbackError?.message || rollbackError}`, recoveryJournal: journalPath }
|
|
2636
|
+
}
|
|
2637
|
+
emit()
|
|
2638
|
+
process.exit(1)
|
|
2639
|
+
}
|